Projections using Hypertuned model through XGboost

All data is from FanGraphs. I have no affiliation with FanGraphs, but please consider contributing to their website if you found this project informative.

1 Project Scope

1.1 Objective

This project is designed to showcase how Using a Percentile Based Worth System values Fantasy Baseball Players through a Inning Pitched (IP) weighted projection

The Categories used for prediction valuation are year-end rankings for the following metrics:

  • Wins
  • Saves
  • Strike Outs
  • ERA ( 9 * Earned Runs per Inning Pitched)
  • WHIP (Walks and Hits per Inning Pitched)
  • Holds



2 Processing the Data

2.1 Getting Data Into R

2.1.1 Load Libraries

First we need to load the packages that R needs to run the analysis

library(sqldf) #SQL in R
library(skimr) #Summaries and useful for removing low % data
library(ggplot2) #Plotting Functions
library(plyr) #slightly deprecated data cleaning
library(dplyr) #slightly updated data cleaning
library(tidyverse) #tidyverse data cleaning universe
library(caret) #wrapper for creating, tuning and validating models
library(xgboost) #package for creating regression tree model
library(vtreat) # useful package for treating data before modeling 
library(Matrix) #creating matricies for xgboost
library(mgcv)
library(moments) #for measuring skewness
library(data.table) #alternative to dplyr we use to create lags
library(pdp) #partial dependence graphs
library(vip) #variable importance 
library(grid) #put multiple plots on one grid
library(gridExtra) #additional grid functionality
library(janitor) #one function used to clean transposed data set
library(ggpubr) #for qq plot 
library(owmr) #Removing Prefixes
library(kableExtra) # formatting HTML Tables
library(formattable) # formatting HTML Tables

The # comments generally explain what additional functionality each library adds to R

2.1.2 Load in Data

All data is downloaded from Fan Graphs from this location. The data is also available on my Github here. There are player level and team data sets


#data read-in
pitcher_data <- read_csv("FanGraphs Leaderboard_Pitching20IP.csv")
#Team datasets
FDG_Team = read_csv("FanGraphs Leaderboard_Team.csv")
#Create a prefix for all team stats that starts with T_
FDG_Team2 <- FDG_Team %>% 
  rename_with( ~ paste0("T_", .x))

2.1.3 Checking Team Data

str give information about an object, while skim provides a customizable summary


#Output not shown for space
#str(FDG_Team2)

skim(FDG_Team2) %>%  
  tibble::as_tibble() #Remove this option for a normal HTML table

2.2 Understanding the Dataset

2.2.1 Exploring the dataset

skim let’s us see how the data was imported into R. Documentation can be found here


#Full Dataset dimensions

skimr::skim(pitcher_data) %>% 
  tibble::as_tibble() %>%  #Remove this option for a normal HTML table
  select(skim_type,skim_variable,complete_rate) %>% 
  filter(complete_rate >0.30) #250 Variables

#skim_type - character or numeric
#skim_variable - name of variable
#complete_rate - % of data that is not missing
#filter - only keep variables that have 30% of data populated

Additionally let’s look at how variables vary by year to see if there are any discrepancies there


#It looks like one year, there were fewer games played, and there is a clear drop off in home runs
pitcher_data_dist =
pitcher_data %>% 
 group_by(Season) %>% 
  summarize (Max_Games = max(G),
             Avg_W= mean(W)
             )

pitcher_data_dist

#Plot Win Data by Year
ggplot(pitcher_data_dist, aes(Season, Avg_W)) +
  geom_col()+
  ggtitle("Average Wins by Year")+
  theme(plot.title = element_text(hjust = 0.5,size = 22,color ="steel blue"))


2.3 Cleaning and Creating Initial Dataset for Model

What are some issues with the data?

  1. Many of Variables, such as K%, are being read in as characters

    • Only Team and Player Name should be characters
  2. There is spotty data coverage in some of the variables (~Variables have less than 30% Coverage)

  3. 2020 Data only includes 60 games worth of data

    • This was a season shortened due to Covid-19
  4. Team Data needs to be appended to pitcher Data by Team Name


2.3.1 Cleanly Changing all Variables that are characters to numeric.

There are several ways to do this, we will identify the variables we want to change that are mis-identified. parse_number can be used to pull numbers from these variables. Additional ways to tackle this can be found here.


#Select Column names that are characters but not Team or Name, These should be percentages
pitcher_data_chars_to_convert <- pitcher_data %>% 
  select_if(is.character)%>% select(-Team,-Name) %>% 
  mutate_all (function(x) as.numeric(readr::parse_number(x))/100)
#Note : There are additional ways to do this, this is just one solution


#We can exclude the variables we converted and reintroduce them
pitcher_data_num <- pitcher_data %>% select(-colnames(pitcher_data_chars_to_convert))

pitcher_data2 = cbind(pitcher_data_num,pitcher_data_chars_to_convert) %>% 
  select (colnames(pitcher_data)) %>%  #preserve original order 
  dplyr::rename(flyball_perc = `FB%...50`,fastball_perc = `FB%...74`) #rename two ambiguous columns
  
skim(pitcher_data2) %>% 
  as_tibble() %>% 
  group_by(skim_type) %>% 
  count()


#Logical variables are R's best guess, in our case they are all NA's and will be removed at a later step

The same can be done for the Team Data that is loaded


#Select Column names that are characters but not Team or Name, These should be percentages
FDG_Team2_chars_to_convert <- FDG_Team2 %>% 
  select_if(is.character)%>% select(-T_Team) %>% 
  mutate_all (function(x) as.numeric(readr::parse_number(x))/100)
#Keep in mind, parse number may make actual characters into numerical variables so carefully check your data before using

#We can exclude the variables we converted and reintroduce them
FDG_Team2_num <- FDG_Team2 %>% select(-colnames(FDG_Team2_chars_to_convert))

FDG_Team3 = cbind(FDG_Team2_num,FDG_Team2_chars_to_convert) %>% 
  select (colnames(FDG_Team2)) %>%  #preserve original order
dplyr::rename(T_flyball_perc = `T_FB%...45`,T_fastball_perc = `T_FB%...72`)  #rename two ambiguous columns

skim(FDG_Team3) %>% 
  as_tibble() %>% 
  group_by(skim_type) %>% 
  count()

2.3.2 Filtering Data with Low Coverage

I choose 30% coverage of data necessary but this can be adjusted up or down. This will also get rid of columns that are all NA.


# Keep variables with enough values (Need 30% data coverage rate here)
Player_cols_to_keep =
skim(pitcher_data2) %>% 
  dplyr::select(skim_type, skim_variable, complete_rate) %>% 
  filter (complete_rate > 0.30)

#Transpose Rows to get column names as skim melts the data
Player_cols_to_keep_transpose = t(Player_cols_to_keep) 

#extract the colnames we would like to keep
Player_cols_to_keep = colnames(janitor::row_to_names(Player_cols_to_keep_transpose,row_number = 2))

#Only keep the columns designated to have over 30% of their data populated or greater
pitcher_data3 = pitcher_data2 %>% 
  select(one_of(Player_cols_to_keep)) 

Repeat the process for Team Variables

Team_cols_to_keep =
skim(FDG_Team3) %>% 
  dplyr::select(skim_type, skim_variable, complete_rate) %>% 
  filter (complete_rate > 0.30)


#Transpose Rows to get column names as skim melts the data
Team_cols_to_keep_transpose = t(Team_cols_to_keep) 

#extract the colnames we would like to keep
Team_cols_to_keep = colnames(janitor::row_to_names(Team_cols_to_keep_transpose,row_number = 2))

#Only keep the columns designated to have over 30% of their data populated or greater
FDG_Team4 = FDG_Team3 %>% 
  select(one_of(Team_cols_to_keep)) 

2.3.3 Creating Variables Normalized by Year

Some Variables will need to be normalized by Innings_Pitched (IP) if they aren’t a percentage already. Remaining Variables are percentages or indices so will not need to be transformed. The full data dictionary for these variables can be found on FanGraph’s website here. for pitching variables and here. for hitting variables.



pitcher_data4 = pitcher_data3 %>% 
  mutate( #create new variables based on existing variables
    W_IP = W/IP,
    L_IP =  L/IP, 
    ShO_IP = ShO/IP,
    SV_IP = SV/IP,
    BS_IP = BS/IP,
    TBF_IP = TBF/IP,
    H_IP = H/IP,
    R_IP = R/IP,
    ER_IP = ER/IP,
    HR_IP=HR/IP,
    BB_IP=BB/IP,
    IBB_IP=IBB/IP,
    HBP_IP=HBP/IP,
    WP_IP= WP/IP,
    BK_IP=BK/IP,
    SO_IP=SO/IP,
    GB_IP = GB/IP,   #Groundballs
    FB_IP =  FB/IP,  #FlyBalls
    LD_IP = LD/IP,   #LineDrives
    IFFB_IP = IFFB/IP,  #Infield Fly balls
    Balls_IP= Balls/IP,
    Strikes_IP= Strikes/IP,
    Pitches_IP= Pitches/IP,
    RS_IP= RS/IP,
    IFH_IP= IFH/IP,
    BU_IP= BU/IP,
    BUH_IP= BUH/IP,
    Pulls_IP= Pulls/IP,
    HLD_IP= HLD/IP,   
    SD_IP= SD/IP,    
    MD_IP= MD/IP,    
    Barrels_IP= Barrels/IP,
    HardHits_IP= HardHit/IP
  ) %>% select(-L,-G,-IP,-ShO,-BS,-(TBF:BK),-(GB:BUH),-Pulls,-(SD:MD),-Barrels,-HardHit,-Events)
               
#will be removed after data is lagged -FIP,-(RAR:WPA),,-(wFB:wCH),-(`ERA-`:`xFIP-`),-SIERA,-(`RA9-WAR`:`Age Rng`),-kwERA,-`wCH (pi)`:`wSL (pi)`,`K/9+`:`HR/FB%+`) 

#skim(pitcher_data4) %>% as_tibble()

Repeat the process for Team Variables


FDG_Team5 = FDG_Team4 %>% 
  mutate( #create new variables based on existing variables
    T_H_T_PA = T_H/T_PA,
    T_x1B_T_PA = T_1B/T_PA, #note: R can't have variables start with a number
    T_x2b_T_PA = T_2B/T_PA,
    T_x3b_T_PA = T_3B/T_PA,
    T_HR_T_PA = T_HR/T_PA,
    T_R_T_PA = T_R/T_PA,
    T_RBI_T_PA = T_RBI/T_PA,
    T_BB_T_PA = T_BB/T_PA,
    T_IBB_T_PA = T_IBB/T_PA,
    T_SO_T_PA=T_SO/T_PA,
    T_HBP_T_PA=T_HBP/T_PA,
    T_SF_T_PA=T_SF/T_PA,
    T_SH_T_PA=T_SH/T_PA,
    T_GDP_T_PA= T_GDP/T_PA,#ground into double play
    T_SB_T_PA=T_SB/T_PA,
    T_CS_T_PA=T_CS/T_PA,
    T_GB_T_PA = T_GB/T_PA,   #Groundballs
    T_FB_T_PA =  T_FB/T_PA,  #FlyBalls
    T_LD_T_PA = T_LD/T_PA,   #LineDrives
    T_IFFB_T_PA = T_IFFB/T_PA,  #Infield Fly balls
    T_Pitches_T_PA= T_Pitches/T_PA,
    T_Balls_T_PA= T_Balls/T_PA,
    T_Strikes_T_PA= T_Strikes/T_PA,
    T_IFH_T_PA= T_IFH/T_PA,
    T_BU_T_PA= T_BU/T_PA,
    T_BUH_T_PA= T_BUH/T_PA,
    T_PH_T_PA= T_PH/T_PA,
    T_Barrels_T_PA= T_Barrels/T_PA,
    T_HardHits_T_PA= T_HardHit/T_PA
  ) %>% select(-(T_H:T_CS),-(T_GB:T_BUH),-T_PH,-T_Barrels,-T_HardHit,-T_Events) #Drop the old variables


#skim(FDG_Team5) %>% as_tibble()

2.3.4 Creating Lagged Variables

There are several ways to lag a dataset BY GROUP.
* Dplyr way is here..
* The data.table (the method used below) is here.

#Note we will only be lagging the player level data, as the previous year's team performance shouldn't impact current performance


#Order the dataset by lag columns
pitcher_data5 =  arrange(pitcher_data4, playerid,Season) #playerid is the Fangraph id assigned to each player

# Convert dataframe to data.table format
DT_pitcher = data.table(pitcher_data5)

#designate columns to lag - which is all of them
cols1 = colnames(pitcher_data5)
anscols = paste("lag", cols1, sep="_")
DT_pitcher[, (anscols) := data.table::shift(.SD, 1, NA, "lag"),by ='playerid', .SDcols=cols1] #Create 1 period lags by year

pitcher_data6 = as.data.frame(DT_pitcher) %>% select(-lag_playerid, -lag_Team, -lag_Season, -lag_Age,-lag_Name)

ncol(pitcher_data5) #251 - no lags
[1] 251
ncol(pitcher_data6) #497 - lagged data ~ (251 * 2)-5
[1] 497

2.3.5 Merging Team and Player Data

We can use either the merge function or the SQL functionality provided by the sqldf package to join the lagged player level data to the Team level data


df_pitching_init = sqldf(
  "
  select a.*, b.*
  from pitcher_data6 a
  left join FDG_Team5 b
  on a.Team = b.T_Team and a.Season = b.T_Season
  
  "
)  %>% select(-T_Team,-T_Season,-T_Age,-T_G,-T_AB)# Unncessary Team Variables


nrow(df_pitching_init) - nrow(pitcher_data6) #check if any rows are duplicated
[1] 0

3 Creating Rankings for Players Based On Percentiles

We can use Percentile based ranking to get rankings for players from the 2021 season.

3.1 Worth of each stat

3.1.1 Calculating past performance

Each player goes from a 0% to 100% on each percentile stat that is used for creating a scoring opportunity. Data is not normalized by IP as certain stats such as Wins will be worth more when we do.


#Categories I include are:
#Wins, Saves, WHIP, ERA, SOs, Holds

df_pitching_init2 =  df_pitching_init %>%
#  arrange(player_id,year) %>% 
  group_by(Season) %>% 
  mutate(
    Wins_share = order(order(rank(W_IP,ties.method = 'average'),decreasing = FALSE))/n(),
     SO_share = order(order(rank(SO_IP,ties.method = 'average'),decreasing = FALSE))/n(),
     SV_share = order(order(rank(SV_IP,ties.method = 'average'),decreasing = FALSE))/n(),
     WHIP_share = order(order(rank(WHIP,ties.method = 'average'),decreasing = FALSE))/n(),
     ERA_share = order(order(rank(ERA,ties.method = 'average'),decreasing = FALSE))/n(),
    HLD_share = order(order(rank(HLD_IP,ties.method = 'average'),decreasing = FALSE))/n(),
    Worth = Wins_share+SO_share+SV_share+WHIP_share+ERA_share+HLD_share
    ) %>% 
  ungroup() 

Chart of the Distribution of initial percentiles
As the chart below shows, the data is roughly normal.


skewness((df_pitching_init2$Worth))
[1] 0.1
ggplot2::qplot(df_pitching_init2$Worth, main="Total Pitching Worth Dataset") + geom_histogram(colour="black", fill="steelblue")
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

min(df_pitching_init2$Worth)
[1] 0.88
max(df_pitching_init2$Worth)
[1] 5.4
ggpubr::ggqqplot(df_pitching_init2$Worth)


shapiro.test(df_pitching_init2$Worth)

    Shapiro-Wilk normality test

data:  df_pitching_init2$Worth
W = 1, p-value = 0.0003

3.2 2021 Player Rankings - Per IP performance

3.2.1 2021 Player Rankings - Top Worth Players with Holds

Total Rankings for the players (Using 6x6 Scoring) can be found here. While it looks like many of the top players have low worth scores, it is because we haven’t applied a modifier for IP yet. Wins are harder to come by relative to any other stat and require more innings pitched.



df_pitching_init2_raw =  df_pitching_init %>%
#  arrange(player_id,year) %>% 
  group_by(Season) %>% 
  mutate(
    Wins_share_raw = order(order(rank(W,ties.method = 'average'),decreasing = FALSE))/n(),
     SO_share_raw = order(order(rank(SO,ties.method = 'average'),decreasing = FALSE))/n(),
     SV_share_raw = order(order(rank(SV,ties.method = 'average'),decreasing = FALSE))/n(),
     WHIP_share = order(order(rank(WHIP,ties.method = 'average'),decreasing = FALSE))/n(),
     ERA_share = order(order(rank(ERA,ties.method = 'average'),decreasing = FALSE))/n(),
    HLD_share_raw = 0,
    Worth = Wins_share_raw+SO_share_raw+SV_share_raw+WHIP_share+ERA_share+HLD_share_raw
    ) %>% 
  ungroup() %>% 
select(-W,-SO,-SV,-WHIP,-ERA,-HLD)



options(digits=2)

df_pitching_init2021_raw =
df_pitching_init2_raw %>% 
  group_by(Name) %>% 
  filter(Season == 2021) %>% 
  arrange(desc(Worth)) %>% 
  select(Name,Wins_share_raw,SO_share_raw,SV_share_raw,WHIP_share,ERA_share,Worth)


df_pitching_init2021_raw %>%
  filter (Worth>3.5) %>% 
  kbl() %>% 
 kable_material(c("striped", "hover","condensed","responsive"),full_width = F,fixed_thead = T)
Name Wins_share_raw SO_share_raw SV_share_raw WHIP_share ERA_share Worth
Daniel Bard 0.79 0.69 0.97 0.87 0.75 4.0
Garrett Richards 0.79 0.84 0.86 0.87 0.68 4.0
Jesus Luzardo 0.77 0.78 0.55 0.89 0.92 3.9
Brady Singer 0.72 0.89 0.66 0.82 0.69 3.8
Brad Keller 0.86 0.85 0.31 0.92 0.77 3.7
Jose Alvarado 0.82 0.60 0.90 0.87 0.51 3.7
Mitch Keller 0.69 0.76 0.40 0.96 0.87 3.7
Justus Sheffield 0.81 0.56 0.37 0.98 0.95 3.7
Nick Pivetta 0.89 0.95 0.72 0.51 0.60 3.7
Rafael Montero 0.65 0.34 0.91 0.81 0.91 3.6
Josh Fleming 0.93 0.58 0.78 0.59 0.73 3.6
Alec Mills 0.74 0.73 0.71 0.69 0.73 3.6
Joe Jimenez 0.75 0.49 0.73 0.80 0.85 3.6
Paul Fry 0.58 0.52 0.83 0.80 0.87 3.6
Wil Crowe 0.61 0.82 0.53 0.84 0.78 3.6
Erick Fedde 0.82 0.87 0.38 0.70 0.78 3.5
Adam Ottavino 0.77 0.62 0.93 0.70 0.51 3.5
Bryan Garcia 0.50 0.22 0.85 0.98 0.98 3.5
Alex Reyes 0.92 0.77 0.98 0.58 0.25 3.5

3.3 2021 Player Rankings - Actual Performance

3.3.1 2021 Player Rankings - Top Worth Players with Holds

While it looks like many of the top players have low worth scores, it is because we haven’t applied a modifier for IP yet.


options(digits=2)

df_pitching_init2021 =
df_pitching_init2 %>% 
  group_by(Name) %>% 
  filter(Season == 2021) %>% 
  arrange(desc(Worth)) %>% 
  select(Name,Wins_share,SO_share,SV_share,WHIP_share,ERA_share,HLD_share,Worth)


df_pitching_init2021 %>%
  filter (Worth>2.9) %>% 
  kbl() %>% 
 kable_material(c("striped", "hover","condensed","responsive"),full_width = F,fixed_thead = T)
Name Wins_share SO_share SV_share WHIP_share ERA_share HLD_share Worth
Paul Fry 0.83 0.87 0.82 0.80 0.87 0.87 5.1
Jose Alvarado 0.96 0.85 0.89 0.87 0.51 0.93 5.0
Daniel Bard 0.93 0.85 0.97 0.87 0.75 0.61 5.0
Joe Jimenez 0.98 0.86 0.76 0.80 0.85 0.72 5.0
Tanner Rainey 0.22 0.93 0.89 0.93 0.97 0.95 4.9
Adam Ottavino 0.95 0.76 0.94 0.70 0.51 0.98 4.8
Rafael Dolis 0.63 0.84 0.89 0.95 0.81 0.68 4.8
Tanner Scott 0.87 0.90 0.40 0.84 0.74 0.93 4.7
Ben Bowden 0.83 0.82 0.57 0.97 0.92 0.53 4.6
Ryan Hendrix 1.00 0.73 0.51 0.82 0.85 0.68 4.6
James Karinchak 0.97 0.96 0.94 0.35 0.47 0.88 4.6
Sean Newcomb 0.62 0.93 0.79 0.93 0.65 0.62 4.5
Pete Fairbanks 0.71 0.92 0.90 0.69 0.35 0.96 4.5
Bryan Abreu 0.83 0.52 0.78 0.73 0.82 0.83 4.5
Jeurys Familia 0.99 0.84 0.72 0.66 0.45 0.81 4.5
Brad Brach 0.24 0.69 0.81 0.87 0.89 0.96 4.5
Rafael Montero 0.91 0.26 0.91 0.81 0.91 0.65 4.5
Rex Brothers 0.55 0.96 0.73 0.68 0.75 0.77 4.4
Lucas Sims 0.92 0.99 0.92 0.20 0.56 0.83 4.4
Bryan Garcia 0.76 0.21 0.85 0.98 0.98 0.64 4.4
Trevor May 0.94 0.92 0.86 0.43 0.35 0.90 4.4
Enyel De Los Santos 0.55 0.94 0.44 0.95 0.90 0.60 4.4
Ryan Helsley 0.97 0.52 0.75 0.67 0.61 0.85 4.4
Aaron Bummer 0.86 0.92 0.81 0.44 0.33 0.98 4.3
Sam Howard 0.66 0.92 0.33 0.73 0.80 0.88 4.3
Amir Garrett 0.04 0.89 0.92 0.83 0.86 0.77 4.3
Tyler Chatwood 0.22 0.80 0.80 0.72 0.81 0.95 4.3
Sean Poppen 0.39 0.79 0.83 0.96 0.74 0.58 4.3
Adam Morgan 0.80 0.72 0.88 0.56 0.53 0.78 4.3
Gregory Soto 0.89 0.83 0.96 0.59 0.28 0.71 4.3
Alex Reyes 0.98 0.90 0.98 0.58 0.25 0.57 4.3
David Hess 0.91 0.32 0.32 1.00 1.00 0.69 4.2
Devin Williams 0.99 0.99 0.86 0.31 0.09 0.99 4.2
Aroldis Chapman 0.93 1.00 0.99 0.52 0.27 0.51 4.2
Trevor Megill 0.35 0.90 0.40 0.98 0.99 0.57 4.2
Phil Maton 0.87 0.88 0.42 0.72 0.65 0.63 4.2
Kyle Finnegan 0.75 0.56 0.93 0.74 0.34 0.84 4.2
Jake Diekman 0.45 0.95 0.90 0.56 0.43 0.87 4.2
Seth Lugo 0.84 0.82 0.75 0.49 0.32 0.92 4.1
Jake Brentz 0.78 0.80 0.80 0.47 0.38 0.88 4.1
J.B. Wendelken 0.87 0.33 0.83 0.78 0.54 0.75 4.1
Jeffrey Springs 0.95 0.96 0.83 0.20 0.30 0.87 4.1
Paul Sewald 0.99 0.99 0.93 0.10 0.19 0.90 4.1
Hansel Robles 0.36 0.70 0.95 0.62 0.58 0.89 4.1
Paul Campbell 0.76 0.49 0.63 0.86 0.91 0.43 4.1
Jacob Barnes 0.26 0.78 0.87 0.73 0.88 0.55 4.1
Josh Sborz 0.67 0.78 0.72 0.67 0.45 0.77 4.1
Carlos Estevez 0.44 0.48 0.94 0.76 0.56 0.89 4.1
Mychal Givens 0.79 0.62 0.92 0.61 0.27 0.85 4.1
Hirokazu Sawamura 0.89 0.77 0.68 0.71 0.19 0.82 4.0
Victor Gonzalez 0.84 0.39 0.78 0.70 0.35 1.00 4.0
Jesus Luzardo 0.63 0.57 0.55 0.89 0.92 0.48 4.0
Mike Mayers 0.66 0.82 0.77 0.49 0.43 0.87 4.0
Sean Doolittle 0.60 0.66 0.74 0.72 0.60 0.70 4.0
Joely Rodriguez 0.36 0.54 0.75 0.81 0.63 0.92 4.0
Anthony Misiewicz 0.87 0.47 0.43 0.64 0.63 0.97 4.0
Brett de Geus 0.59 0.21 0.65 0.95 0.98 0.60 4.0
Chris Stratton 0.86 0.67 0.90 0.50 0.37 0.67 4.0
Matt Foster 0.49 0.55 0.77 0.70 0.85 0.59 4.0
Heath Hembree 0.25 0.96 0.92 0.30 0.79 0.73 4.0
Camilo Doval 1.00 0.94 0.90 0.13 0.17 0.81 4.0
Kyle Zimmer 0.74 0.26 0.81 0.65 0.68 0.81 4.0
Shane Greene 0.01 0.57 0.83 0.91 0.96 0.66 3.9
Darwinzon Hernandez 0.47 0.93 0.53 0.77 0.28 0.94 3.9
Archie Bradley 0.98 0.17 0.82 0.68 0.39 0.90 3.9
Brad Boxberger 0.78 0.90 0.86 0.15 0.26 0.98 3.9
Sean Reid-Foley 0.90 0.89 0.36 0.77 0.75 0.24 3.9
Matt Andriese 0.33 0.59 0.74 0.88 0.75 0.62 3.9
Reid Detmers 0.44 0.39 0.67 0.97 0.97 0.46 3.9
Kevin Ginkel 0.09 0.70 0.54 0.82 0.89 0.85 3.9
Stefan Crichton 0.05 0.11 0.93 0.99 0.96 0.86 3.9
Luis Oviedo 0.24 0.62 0.63 0.99 0.99 0.42 3.9
Jose Cisnero 0.64 0.53 0.87 0.54 0.38 0.93 3.9
Codi Heuer 0.92 0.22 0.79 0.53 0.54 0.90 3.9
Glenn Otto 0.10 0.83 0.61 0.93 0.99 0.41 3.9
Aaron Ashby 0.90 0.86 0.80 0.28 0.61 0.44 3.9
Daniel Norris 0.26 0.54 0.72 0.76 0.87 0.73 3.9
Greg Holland 0.52 0.43 0.91 0.57 0.68 0.76 3.9
Nick Mears 0.36 0.50 0.66 0.90 0.71 0.74 3.9
Brooks Raley 0.32 0.92 0.82 0.32 0.67 0.81 3.8
Joakim Soria 0.17 0.66 0.92 0.61 0.72 0.74 3.8
Sam Coonrod 0.42 0.75 0.84 0.53 0.47 0.82 3.8
Jacob Webb 0.98 0.45 0.79 0.79 0.50 0.31 3.8
Richard Lovelady 0.91 0.74 0.85 0.15 0.32 0.84 3.8
Austin Voth 0.70 0.57 0.29 0.74 0.76 0.73 3.8
Corey Knebel 0.99 0.81 0.90 0.06 0.08 0.91 3.8
Trevor Stephan 0.42 0.81 0.71 0.66 0.57 0.59 3.8
Griffin Canning 0.80 0.51 0.54 0.75 0.80 0.35 3.8
Hector Neris 0.51 0.91 0.93 0.26 0.37 0.77 3.8
Demarcus Evans 0.10 0.87 0.60 0.81 0.74 0.64 3.8
Bryan Shaw 0.77 0.36 0.77 0.62 0.32 0.90 3.7
Anthony Castro 0.32 0.91 0.82 0.44 0.66 0.57 3.7
Brad Hand 0.88 0.41 0.97 0.45 0.44 0.58 3.7
Taylor Rogers 0.46 0.97 0.95 0.23 0.27 0.84 3.7
Tim Hill 0.91 0.41 0.72 0.40 0.37 0.93 3.7
Chad Green 0.96 0.81 0.87 0.02 0.20 0.86 3.7
Kyle Keller 0.21 0.67 0.47 0.83 0.92 0.61 3.7
Mason Thompson 0.33 0.41 0.63 0.98 0.46 0.89 3.7
Alex Lange 0.19 0.71 0.78 0.77 0.47 0.79 3.7
Diego Castillo 0.84 0.89 0.96 0.08 0.13 0.80 3.7
Anthony Kay 0.20 0.79 0.59 0.92 0.81 0.39 3.7
Josh Taylor 0.15 0.87 0.75 0.68 0.29 0.95 3.7
Conner Greene 0.30 0.58 0.35 0.95 0.95 0.56 3.7
Scott Barlow 0.67 0.85 0.95 0.32 0.07 0.82 3.7
Aaron Slegers 0.64 0.20 0.30 0.98 0.95 0.62 3.7
Kyle Funkhouser 0.92 0.37 0.71 0.65 0.30 0.74 3.7
Tommy Nance 0.26 0.63 0.47 0.55 0.96 0.80 3.7
Spencer Howard 0.11 0.62 0.65 0.88 0.97 0.44 3.7
Edwin Uceta 0.10 0.86 0.59 0.80 0.93 0.39 3.7
Michael Lorenzen 0.25 0.10 0.91 0.63 0.80 0.98 3.7
Brusdar Graterol 0.87 0.20 0.58 0.66 0.62 0.73 3.7
Jordan Romano 0.94 0.93 0.97 0.13 0.04 0.64 3.7
Michael Fulmer 0.72 0.61 0.94 0.47 0.17 0.74 3.7
Alex Colome 0.61 0.31 0.96 0.64 0.49 0.64 3.7
Yimi Garcia 0.69 0.60 0.96 0.25 0.51 0.63 3.7
Spencer Patton 0.42 0.75 0.84 0.32 0.42 0.90 3.6
Alex Claudio 0.22 0.38 0.79 0.86 0.79 0.61 3.6
Matt Barnes 0.94 0.98 0.98 0.21 0.41 0.11 3.6
Matt Wisler 0.62 0.89 0.74 0.15 0.39 0.85 3.6
Rowan Wick 0.04 0.86 0.95 0.58 0.54 0.66 3.6
Wander Suero 0.42 0.60 0.31 0.66 0.89 0.75 3.6
Justin Garza 0.71 0.56 0.53 0.84 0.65 0.34 3.6
Cionel Perez 0.34 0.59 0.51 0.93 0.90 0.34 3.6
Blake Treinen 0.82 0.80 0.89 0.07 0.04 0.99 3.6
Giovanny Gallegos 0.74 0.80 0.93 0.02 0.18 0.94 3.6
Wandy Peralta 0.90 0.24 0.89 0.62 0.27 0.69 3.6
Max Kranick 0.50 0.23 0.63 0.94 0.89 0.43 3.6
Michael Rucker 0.08 0.63 0.81 0.80 0.95 0.33 3.6
J.D. Hammer 0.47 0.70 0.51 0.88 0.70 0.34 3.6
Tim Mayza 0.89 0.65 0.73 0.08 0.29 0.97 3.6
Zac Lowther 0.24 0.56 0.57 0.91 0.93 0.38 3.6
Ryne Stanek 0.37 0.84 0.79 0.34 0.30 0.95 3.6
Dylan Floro 0.88 0.45 0.96 0.36 0.14 0.79 3.6
Jackson Kowar 0.10 0.44 0.62 1.00 1.00 0.42 3.6
Cesar Valdez 0.36 0.47 0.93 0.91 0.84 0.06 3.6
Justus Sheffield 0.85 0.17 0.37 0.98 0.95 0.25 3.6
Lucas Gilbreath 0.72 0.60 0.76 0.52 0.28 0.68 3.6
Albert Abreu 0.52 0.44 0.78 0.42 0.74 0.65 3.5
Lou Trivino 0.89 0.34 0.97 0.41 0.22 0.71 3.5
Michael Feliz 0.02 0.70 0.85 0.91 0.96 0.10 3.5
Phillips Valdez 0.47 0.29 0.76 0.58 0.84 0.59 3.5
Garrett Richards 0.49 0.24 0.75 0.87 0.68 0.50 3.5
Chris Rodriguez 0.68 0.50 0.64 0.71 0.38 0.63 3.5
Junior Guerra 0.76 0.38 0.05 0.94 0.86 0.54 3.5
Trevor Richards 0.93 0.84 0.71 0.05 0.32 0.68 3.5
Keynan Middleton 0.23 0.16 0.91 0.85 0.69 0.69 3.5
Daniel Lynch 0.58 0.20 0.62 0.90 0.81 0.41 3.5
Edward Cabrera 0.10 0.64 0.62 0.90 0.83 0.42 3.5
Tyler Zuber 0.09 0.36 0.58 0.84 0.88 0.76 3.5
Adam Plutko 0.13 0.17 0.73 0.90 0.94 0.67 3.5
Brent Suter 1.00 0.40 0.70 0.52 0.20 0.71 3.5
Jose Quijada 0.07 0.98 0.48 0.60 0.61 0.78 3.5
Shawn Armstrong 0.18 0.84 0.17 0.77 0.94 0.60 3.5
Garrett Crochet 0.53 0.83 0.67 0.46 0.15 0.86 3.5
Garrett Whitlock 0.94 0.72 0.77 0.20 0.03 0.83 3.5
Cole Sulser 0.80 0.77 0.91 0.21 0.12 0.68 3.5
Jose Quintana 0.02 0.93 0.13 0.94 0.91 0.54 3.5
Josh Fleming 0.89 0.04 0.69 0.59 0.73 0.54 3.5
A.J. Minter 0.56 0.68 0.46 0.37 0.41 0.99 3.5
Brady Singer 0.29 0.54 0.66 0.82 0.69 0.46 3.5
Alex Young 0.29 0.22 0.44 0.96 0.92 0.64 3.5
Genesis Cabrera 0.56 0.70 0.39 0.44 0.40 0.99 3.5
Brandon Bielak 0.59 0.36 0.74 0.63 0.59 0.56 3.5
Alex Vesia 0.75 0.94 0.77 0.08 0.06 0.86 3.4
Patrick Murphy 0.08 0.58 0.52 0.72 0.74 0.81 3.4
Andrew Wantz 0.28 0.95 0.66 0.40 0.70 0.45 3.4
Emilio Pagan 0.63 0.68 0.27 0.26 0.68 0.91 3.4
Craig Kimbrel 0.67 1.00 0.98 0.02 0.06 0.70 3.4
Bailey Falter 0.59 0.55 0.56 0.32 0.81 0.61 3.4
Wade Davis 0.01 0.32 0.84 0.74 0.94 0.58 3.4
Austin Davis 0.29 0.64 0.37 0.62 0.73 0.78 3.4
Derek Holland 0.60 0.59 0.03 0.83 0.72 0.65 3.4
Will Vest 0.19 0.16 0.53 0.88 0.87 0.79 3.4
Kendall Graveman 0.86 0.68 0.94 0.08 0.02 0.84 3.4
Jay Jackson 0.88 0.91 0.07 0.40 0.40 0.75 3.4
Dinelson Lamet 0.34 0.83 0.37 0.76 0.57 0.52 3.4
J.P. Feyereisen 0.72 0.41 0.86 0.38 0.12 0.91 3.4
Yency Almonte 0.14 0.50 0.29 0.86 0.97 0.62 3.4
Brandon Kintzler 0.68 0.13 0.10 0.96 0.90 0.63 3.4
Josh Staumont 0.61 0.71 0.88 0.16 0.16 0.89 3.4
Austin Adams 0.56 0.97 0.24 0.32 0.48 0.83 3.4
Phil Bickford 0.79 0.77 0.73 0.16 0.14 0.80 3.4
Keegan Thompson 0.54 0.58 0.73 0.74 0.28 0.51 3.4
Bruce Zimmermann 0.62 0.29 0.58 0.79 0.72 0.39 3.4
Miguel Diaz 0.72 0.68 0.76 0.31 0.37 0.52 3.4
Anthony Banda 0.59 0.44 0.26 0.81 0.53 0.72 3.4
Humberto Mejia 0.10 0.33 0.60 0.98 0.96 0.40 3.4
Edwin Diaz 0.80 0.96 0.99 0.13 0.31 0.18 3.4
Buck Farmer 0.04 0.61 0.27 0.94 0.90 0.60 3.4
Jonathan Loaisiga 0.97 0.49 0.87 0.10 0.05 0.88 3.4
Shane McClanahan 0.81 0.76 0.62 0.46 0.31 0.41 3.4
Josiah Gray 0.19 0.67 0.66 0.60 0.78 0.45 3.4
Jeff Hoffman 0.32 0.66 0.38 0.85 0.61 0.53 3.4
Caleb Baragar 0.84 0.08 0.88 0.59 0.02 0.94 3.4
Miguel Sanchez 0.77 0.31 0.56 0.85 0.49 0.37 3.4
Hoby Milner 0.03 0.96 0.20 0.79 0.77 0.58 3.3
Braxton Garrett 0.20 0.40 0.63 0.97 0.71 0.42 3.3
Luke Farrell 0.32 0.57 0.29 0.92 0.66 0.57 3.3
Dylan Cease 0.79 0.94 0.45 0.42 0.44 0.28 3.3
A.J. Alexy 0.97 0.11 0.64 0.51 0.65 0.43 3.3
Nick Wittgren 0.23 0.48 0.71 0.41 0.72 0.76 3.3
Sergio Romo 0.12 0.48 0.84 0.40 0.64 0.83 3.3
Andrew Kittredge 0.96 0.66 0.90 0.07 0.03 0.69 3.3
JC Mejia 0.13 0.33 0.59 0.88 0.99 0.40 3.3
Cody Poteet 0.66 0.62 0.47 0.57 0.71 0.29 3.3
Daniel Hudson 0.90 0.97 0.07 0.17 0.26 0.95 3.3
Tyler Duffey 0.43 0.48 0.84 0.36 0.22 0.97 3.3
Mitch Keller 0.46 0.35 0.40 0.96 0.87 0.26 3.3
Ryan Borucki 0.97 0.34 0.34 0.38 0.70 0.57 3.3
Clay Holmes 0.95 0.72 0.22 0.26 0.36 0.78 3.3
Tejay Antone 0.59 0.87 0.88 0.02 0.04 0.88 3.3
Nick Pivetta 0.57 0.74 0.68 0.51 0.60 0.20 3.3
Blake Taylor 0.89 0.46 0.34 0.66 0.22 0.72 3.3
Michael Kopech 0.57 0.98 0.37 0.22 0.32 0.82 3.3
Tarik Skubal 0.51 0.69 0.64 0.45 0.55 0.43 3.3
Justin Steele 0.70 0.58 0.38 0.58 0.53 0.50 3.3
Joe Smith 0.92 0.28 0.02 0.64 0.71 0.70 3.3
Sam Hentges 0.12 0.51 0.45 0.96 0.93 0.28 3.3
Brandon Workman 0.27 0.32 0.14 0.99 0.78 0.76 3.2
Raisel Iglesias 0.91 0.98 0.99 0.04 0.10 0.25 3.2
Andrew Heaney 0.62 0.77 0.30 0.53 0.84 0.19 3.2
Sammy Long 0.45 0.40 0.66 0.48 0.79 0.46 3.2
Ryan Pressly 0.78 0.87 0.98 0.06 0.06 0.50 3.2
Josh Tomlin 0.81 0.13 0.09 0.77 0.92 0.52 3.2
Kohei Arihara 0.45 0.03 0.67 0.69 0.93 0.46 3.2
Eduardo Rodriguez 0.82 0.79 0.19 0.64 0.66 0.13 3.2
Chad Kuhl 0.62 0.38 0.28 0.68 0.68 0.59 3.2
Robert Stephenson 0.36 0.74 0.75 0.50 0.20 0.66 3.2
Travis Lakins Sr. 0.27 0.27 0.46 0.69 0.83 0.71 3.2
Dan Winkler 0.17 0.54 0.16 0.82 0.75 0.77 3.2
David Peterson 0.21 0.60 0.57 0.65 0.79 0.38 3.2
Carson Fulmer 0.06 0.42 0.43 0.80 0.93 0.56 3.2
Tylor Megill 0.39 0.72 0.61 0.48 0.60 0.41 3.2
Connor Brogdon 0.85 0.29 0.72 0.23 0.30 0.80 3.2
Caleb Thielbar 0.93 0.83 0.10 0.26 0.25 0.82 3.2
Taylor Clarke 0.16 0.33 0.40 0.80 0.71 0.79 3.2
Tony Gonsolin 0.73 0.79 0.50 0.59 0.25 0.32 3.2
Yusmeiro Petit 0.92 0.01 0.77 0.12 0.44 0.92 3.2
Anthony Bender 0.44 0.78 0.84 0.15 0.13 0.84 3.2
David Price 0.68 0.18 0.69 0.67 0.46 0.49 3.2
Austin Warren 0.99 0.50 0.85 0.11 0.02 0.69 3.2
Wil Crowe 0.25 0.42 0.53 0.84 0.78 0.35 3.2
Alexander Wells 0.42 0.03 0.53 0.89 0.94 0.35 3.2
Luis Patino 0.64 0.43 0.65 0.46 0.54 0.44 3.2
David Bednar 0.46 0.88 0.85 0.07 0.05 0.86 3.2
Chris Martin 0.41 0.15 0.76 0.45 0.45 0.94 3.2
John King 0.99 0.28 0.63 0.25 0.33 0.66 3.1
Sam Clay 0.05 0.14 0.34 0.93 0.80 0.89 3.1
Daniel Ponce de Leon 0.21 0.10 0.86 0.89 0.87 0.21 3.1
Cristian Javier 0.30 0.88 0.74 0.29 0.34 0.59 3.1
Randy Dobnak 0.14 0.01 0.74 0.82 0.98 0.45 3.1
Sean Guenther 0.09 0.12 0.56 0.99 1.00 0.37 3.1
Brad Keller 0.59 0.32 0.31 0.92 0.77 0.20 3.1
Jordan Holloway 0.54 0.53 0.60 0.60 0.46 0.40 3.1
Shaun Anderson 0.07 0.21 0.51 1.00 0.99 0.33 3.1
Logan Gilbert 0.47 0.65 0.64 0.27 0.65 0.43 3.1
Wes Benjamin 0.07 0.26 0.48 0.99 0.99 0.30 3.1
Jake Cousins 0.24 0.97 0.62 0.27 0.12 0.87 3.1
Collin McHugh 0.88 0.77 0.71 0.04 0.02 0.68 3.1
Chris Paddack 0.64 0.35 0.56 0.45 0.73 0.37 3.1
Jorge Alcala 0.48 0.56 0.72 0.07 0.45 0.81 3.1
Packy Naughton 0.11 0.01 0.65 0.97 0.90 0.44 3.1
Junior Fernandez 0.44 0.12 0.44 0.99 0.81 0.28 3.1
Luis Garcia 0.71 0.65 0.66 0.30 0.32 0.45 3.1
Yusei Kikuchi 0.38 0.59 0.60 0.54 0.57 0.40 3.1
Dominic Leone 0.75 0.39 0.81 0.19 0.01 0.92 3.1
Matt Peacock 0.57 0.02 0.58 0.83 0.69 0.38 3.1
Dean Kremer 0.07 0.31 0.50 0.90 0.98 0.32 3.1
Vladimir Gutierrez 0.79 0.16 0.49 0.66 0.66 0.31 3.1
Pierce Johnson 0.49 0.91 0.21 0.44 0.24 0.78 3.1
Shane Bieber 0.73 0.95 0.51 0.34 0.22 0.33 3.1
Alek Manoah 0.81 0.75 0.67 0.13 0.24 0.46 3.1
Joe Kelly 0.39 0.74 0.83 0.07 0.15 0.87 3.1
Griffin Jax 0.43 0.18 0.57 0.59 0.90 0.38 3.1
Matt Manning 0.41 0.06 0.58 0.79 0.83 0.38 3.1
Hyeon-jong Yang 0.11 0.09 0.67 0.91 0.80 0.47 3.1
Zack Littell 0.65 0.56 0.80 0.23 0.16 0.65 3.0
Erick Fedde 0.50 0.44 0.38 0.70 0.78 0.25 3.0
Kenta Maeda 0.54 0.63 0.45 0.51 0.64 0.28 3.0
Cody Ponce 0.06 0.40 0.41 0.95 0.95 0.26 3.0
Jharel Cotton 0.66 0.50 0.22 0.65 0.33 0.69 3.0
Liam Hendriks 0.95 0.99 0.99 0.01 0.09 0.02 3.0
Jorge Lopez 0.16 0.36 0.25 0.89 0.86 0.50 3.0
Caleb Smith 0.26 0.69 0.28 0.62 0.68 0.51 3.0
Taylor Widener 0.19 0.59 0.48 0.71 0.56 0.49 3.0
Ryan Thompson 0.86 0.68 0.35 0.11 0.07 0.96 3.0
Art Warren 0.98 0.99 0.43 0.02 0.01 0.59 3.0
Ryan Weathers 0.34 0.15 0.69 0.63 0.76 0.45 3.0
Tyler Rogers 0.84 0.07 0.92 0.16 0.05 0.98 3.0
Andrew Miller 0.01 0.72 0.06 0.85 0.67 0.72 3.0
Keegan Akin 0.14 0.28 0.50 0.85 0.93 0.32 3.0
Jarlin Garcia 0.85 0.51 0.70 0.05 0.11 0.79 3.0
Joe Ryan 0.76 0.75 0.61 0.01 0.47 0.41 3.0
Eli Morgan 0.54 0.34 0.57 0.43 0.76 0.37 3.0
Tyler Kinley 0.35 0.46 0.43 0.34 0.66 0.76 3.0
Yohan Ramirez 0.27 0.89 0.88 0.18 0.44 0.33 3.0
Taylor Hearn 0.56 0.31 0.43 0.53 0.63 0.48 3.0
Yacksel Rios 0.94 0.25 0.28 0.39 0.53 0.55 2.9
Tyler Clippard 0.30 0.23 0.96 0.50 0.23 0.72 2.9
Carlos Hernandez 0.71 0.28 0.65 0.48 0.38 0.44 2.9
Kris Bubic 0.40 0.30 0.61 0.63 0.58 0.41 2.9
Johan Oviedo 0.11 0.21 0.65 0.84 0.69 0.44 2.9
Dane Dunning 0.35 0.46 0.50 0.70 0.59 0.32 2.9
Nick Sandlin 0.20 0.97 0.59 0.23 0.17 0.77 2.9
Kyle Muller 0.53 0.54 0.56 0.42 0.50 0.37 2.9
Luis Garcia 0.21 0.56 0.86 0.08 0.25 0.96 2.9
Luis Gil 0.25 0.90 0.60 0.55 0.20 0.40 2.9
Robert Dugger 0.07 0.13 0.47 0.97 0.96 0.30 2.9

4 Creating Model File

4.1 Additional Data Prep

4.1.1 Remove Variables which are based off current hitting numbers

Not all variables can be used for predictive modeling. Variables that go into the percentile ranking or are non-normalized metrics created after the fact (such as WAR - Wins above Replacement or RS - Raw Run Support) should be removed. However, metrics that are normalized by a per pitch basis (such as wFB/C) can remain as we expect pitchers to have similar performance in these metrics one year out.

#Be careful about RS - Run Support and RS/9

#Creating a new dataset to keep original intact
df_pitching_init3 = df_pitching_init2 %>% 
  select (-Name)

Lagged Percentile (_share) Variables can be used for predictive modeling. However since these variables were created for the Worth metric they must also be removed for modeling purposes.


#Order the dataset by lag columns
df_pitching_init4 =  arrange(df_pitching_init3, playerid,Season) #playerid is the Fangraph id assigned to each player

# Convert dataframe to data.table format
DT_pitcher2 = data.table(df_pitching_init4)

#designate columns to lag - just the new shares
cols1 = (c('Wins_share','SO_share','SV_share', 'ERA_share','WHIP_share','HLD_share','Worth'))
anscols = paste("lag", cols1, sep="_") 
DT_pitcher2[, (anscols) := data.table::shift(.SD, 1, NA, "lag"),by ='playerid', .SDcols=cols1] #Create 1 period lags by year

df_pitching_final = as.data.frame(DT_pitcher2) %>% 
  select(-c(Wins_share,SO_share,SV_share, ERA_share,WHIP_share,HLD_share))%>%
select(-FIP,-(RAR:WPA),-(wFB:wCH),-(`ERA-`:`xFIP-`),
       -SIERA,-(`RA9-WAR`:`Age Rng`),-kwERA,-(`wCH (pi)`:`wSL (pi)`),-(`K/9+`:`HR/FB%+`)) %>% select(-W,-SO,-SV,-HLD,-W_IP,-SO_IP,-SV_IP,-WHIP,-ERA,-HLD_IP)

4.1.2 Creating Training/Test Split

We split the data into Training Data (which is used to create the model) and test data (which is used to validate the model)


set.seed(15674)  # For reproducibility
# Create index for testing and training data
inTrain <- createDataPartition(y = df_pitching_final$Worth, p = 0.80, list = FALSE)
# subset pitching data for training
tr_2021 <- df_pitching_final[inTrain,]
# subset the rest to test and validate trained model
te_2021 <- df_pitching_final[-inTrain,]

nrow(tr_2021)/nrow(df_pitching_final) #check if split is 0.8
[1] 0.8

4.1.3 Treat Missing Data by Imputing Mean Value

Vtreat Package in R is excellent for treating data before using for modeling. Additional documentation can be found here.

treat_plan_2021 <- vtreat::designTreatmentsZ(
  dframe = tr_2021, # training data
  varlist = colnames(tr_2021) %>% .[. != "hitting_score1"], # input variables = all training data columns, except random
  codeRestriction = c("clean", "isBAD", "lev"), # derived variables types (drop cat_P)
  verbose = FALSE) # suppress messages

#clean stands for cleaned numerical variable, isBAD indicates that a value replacement has occurred (which indicates a missing value in this case), and lev is a binary indicator whether a particular value of that categorical variable was present.  

#### Checking Scoreframe

score_frame <- treat_plan_2021$scoreFrame %>% 
  select(varName, origName, code)

head(score_frame)


tr_treated_2021 <- vtreat::prepare(treat_plan_2021, tr_2021)
te_treated_2021 <- vtreat::prepare(treat_plan_2021, te_2021)


treat_plan_2021 <- vtreat::designTreatmentsZ(
  dframe = DT_pitcher2, # training data
  varlist = colnames(DT_pitcher2) %>% .[. != "hitting_score1"], # input variables = all training data columns, except random
  codeRestriction = c("clean", "isBAD", "lev"), # derived variables types (drop cat_P)
  verbose = FALSE) # suppress messages


total_treated_2021_pitching <- vtreat::prepare(treat_plan_2021, DT_pitcher2)

#tr_treated = tr
#te_treated = te

dim(tr_treated_2021) #note there are dummies for each player and team
[1] 3197 1416

4.1.4 Check Distribution of Training Population

The population used for Training should be indicative of Total Population


ggplot2::qplot(tr_treated_2021$Worth, main="Training Set") + geom_histogram(colour="black", fill="steelblue") + theme_bw()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

#The skewness is actually a bit better than the overall data set
skewness(tr_treated_2021$Worth) 
[1] 0.1

5 Running XGboost Model

To keep things simple with modeling, we’ll turn the training data into simple input variables for caret::train, dropping the response variable and converting the data frame to a matrix. Documentation for this approach to XGboost can be found here.

5.1 Tuning the Model

5.1.1 Initial Non-Tuned Model

Break the data set into x and y inputs with x being a matrix. "_isBAD" is a category created by the Vtreat package in case you want to identify rows

input_x <- as.matrix(((tr_treated_2021))%>%
   select(-Worth) %>%                      
   select(!ends_with ("_isBAD")))

input_y <- tr_treated_2021$Worth

XGBoost with Default Hyperparameters:
The Variable Importance (caret::varImp(xgb_base_2021, scale = F) from the caret package shows the contribution of each variable to the initial model. Since this is untuned, we can expect the percentage imporantance to change as the models iterate through potential hyperparameters.
XGBoost documentation can be found for more general models here.


#Defaults for xgboost model
grid_default <- expand.grid(
  nrounds = 100,
  max_depth = 6,
  eta = 0.3,
  gamma = 0,
  colsample_bytree = 1,
  min_child_weight = 1,
  subsample = 1
)

#This is a blank train_control set, this will be updated after
train_control <- caret::trainControl(
  method = "none",
  verboseIter = FALSE, # no training log
  allowParallel = TRUE # FALSE for reproducible results 
)

xgb_base_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = train_control,
  tuneGrid = grid_default,
  method = "xgbTree",
  verbose = TRUE
)

caret::varImp(xgb_base_2021, scale = F  )
xgbTree variable importance

  only 20 most important variables shown (out of 774)

5.2 Further Variable Selection

5.2.1 Remove redundant and highly correlated variables

Selection Removal Step 1: Check for high correlations
Normally, this step is done early, but those steps were reserved for preparing the data


dep_cor1 <- t(as.data.frame(cor(tr_treated_2021[ , colnames(tr_treated_2021) != "Worth"],
                tr_treated_2021$Worth)))
dep_cor1 <-
as.data.frame(t(as.data.frame(dep_cor1)%>% 
  select(!starts_with("lag")) %>% #remove lag variables
  select(!contains("_isBAD")))) 

dep_cor1 <- tibble::rownames_to_column(dep_cor1,"VARIABLES")%>% #remove indicators for missing data
  filter(V1 > 0.40|V1 < -0.3)

dep_cor1

dep_cor2 <- colnames(row_to_names(t(dep_cor1),row_number = 1))

Let’s Remove variables with high correlation to worth metric, and metrics that are calculated after a player’s performance (such as WPA/RE24) or redundant (RS_IP)


input_x <- as.matrix(((tr_treated_2021))%>%
   select(-Worth) %>% #Remove some variables variables
     select (-RS_IP,-ER_IP,-R_IP,-REW,-RE24,-Clutch,-WPA_slash_LI,-Season #Remove redundant variables or non/weighted variables
) %>%      
select(!ends_with ("_isBAD"))) #indicator variable for missing data

input_y <- tr_treated_2021$Worth

Run the model on the new dataset to make sure the variable importances look fine


#Note Training parameters were set in initial model set up
xgb_base_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = train_control,
  tuneGrid = grid_default,
  method = "xgbTree",
  verbose = TRUE
)

caret::varImp(xgb_base_2021, scale = F  )
xgbTree variable importance

  only 20 most important variables shown (out of 766)

5.3 Model with new data

5.3.1 Tuning All Hyperparameters

A tune grid allows us to test a large amount of hyper-parameters and find the model with the lowest RMSE for predictions.
However, The more values you want to test and the greater the amount of Cross-Fold Validations (method = "cv"), the greater the computational time it will take. More information on the specific parameters can be found here.


# maximum number of trees
nrounds <- 1000

# note to start nrounds from 200, as smaller learning rates result in errors so
# big with lower starting points that they'll mess the scales
tune_grid <- expand.grid(
  nrounds = seq(from = 100, to = nrounds, by = 50),
  eta = c(0.01, 0.025, 0.05, 0.075, 0.1),
  max_depth = c(2, 4, 6, 8, 10),
  gamma = 0,
  colsample_bytree = 1,
  min_child_weight = 1,
  subsample = 1
)

tune_control <- caret::trainControl(
  method = "cv", # cross-validation
  number = 5, # with n folds 
  ## Note this was # out in the original code
  #index = createFolds(tr_treated$Id_clean), # fix the folds
  verboseIter = FALSE, # no training log
  allowParallel = FALSE # FALSE for reproducible results 
)

Running the initial tuning model

#Note I will be timing these runs to give an estimate on how long this model takes to run
start_time <- Sys.time()

xgb_tune_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid,
  method = "xgbTree",
  verbose = FALSE
  ,verbosity = 0
)

end_time <- Sys.time()

end_time - start_time
Time difference of 21 mins

Tuning Plot and Variable Importance

varImp(xgb_tune_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 766)
# helper function for the plots
tuneplot <- function(x, probs = .90) {
  ggplot(x) +
    coord_cartesian(ylim = c(quantile(x$results$RMSE, probs = probs), min(x$results$RMSE))) +
    theme_bw()
}

tuneplot(xgb_tune_2021)


5.3.2 Fine Tuning Model

5.3.2.1 Second Tuning: Maximum Depth and Minimum Child Weight

After fixing the learning rate to the best tune from the previous iteration and we’ll also set maximum depth to 3 +-1 (or +2 if max_depth == 2) to experiment a bit around the suggested best tune in previous step. Then, well fix maximum depth and minimum child weight.

tune_grid2 <- expand.grid(
  nrounds = seq(from = 50, to = nrounds, by = 50),
  eta = xgb_tune_2021$bestTune$eta,
  max_depth = ifelse(xgb_tune_2021$bestTune$max_depth == 2,
    c(xgb_tune_2021$bestTune$max_depth:4),
    xgb_tune_2021$bestTune$max_depth - 1:xgb_tune_2021$bestTune$max_depth + 1),
  gamma = 0,
  colsample_bytree = 1,
  min_child_weight = c(1, 2, 3),
  subsample = 1
)

xgb_tune2_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid2,
  method = "xgbTree",
  verbose = TRUE
)

tuneplot(xgb_tune2_2021)


xgb_tune2_2021$bestTune

varImp(xgb_tune2_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 766)

5.3.2.2 Third Tuning: Column and Row Sampling


tune_grid3 <- expand.grid(
  nrounds = seq(from = 50, to = nrounds, by = 50),
  eta = xgb_tune_2021$bestTune$eta,
  max_depth = xgb_tune2_2021$bestTune$max_depth,
  gamma = 0,
  colsample_bytree = c(0.4, 0.6, 0.8, 1.0),
  min_child_weight = xgb_tune2_2021$bestTune$min_child_weight,
  subsample = c(0.5, 0.75, 1.0)
)

xgb_tune3_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid3,
  method = "xgbTree",
  verbose = TRUE
)

tuneplot(xgb_tune3_2021, probs = .95)


xgb_tune3_2021$bestTune

varImp(xgb_tune3_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 766)

5.3.2.3 Fourth Tuning: Gamma

Next, we again pick the best values from previous step, and now will see whether changing the gamma has any effect on the model fit:

tune_grid4 <- expand.grid(
  nrounds = seq(from = 50, to = nrounds, by = 50),
  eta = xgb_tune_2021$bestTune$eta,
  max_depth = xgb_tune2_2021$bestTune$max_depth,
  gamma = c(0, 0.05,0.1, 0.2,0.4, 0.5, 0.7, 0.9, 1.0),
  colsample_bytree = xgb_tune3_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune2_2021$bestTune$min_child_weight,
  subsample = xgb_tune3_2021$bestTune$subsample
)

xgb_tune4_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid4,
  method = "xgbTree",
  verbose = TRUE
)

tuneplot(xgb_tune4_2021)
Warning: The shape palette can deal with a maximum of 6 discrete values because more than 6
becomes difficult to discriminate; you have 9. Consider specifying shapes manually if
you must have them.
Warning: Removed 60 rows containing missing values (geom_point).

xgb_tune4_2021$bestTune

varImp(xgb_tune4_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 766)

5.3.2.4 Fifth Tuning: Reducing the Learning Rate

Now, we have tuned the hyperparameters and can start reducing the learning rate to get to the final model:

start_time <- Sys.time()

tune_grid5 <- expand.grid(
  nrounds = seq(from = 100, to = 10000, by = 75),
   eta = c(0.01, 0.015, 0.025,0.035, 0.05,0.75, 0.1),
  max_depth = xgb_tune2_2021$bestTune$max_depth,
  gamma = xgb_tune4_2021$bestTune$gamma,
  colsample_bytree = xgb_tune3_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune2_2021$bestTune$min_child_weight,
  subsample = xgb_tune3_2021$bestTune$subsample
)



xgb_tune5_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid5,
  method = "xgbTree",
  verbose = TRUE
)

#tuneplot(xgb_tune5_2021)

end_time <- Sys.time()

end_time - start_time
Time difference of 17 mins
xgb_tune5_2021$bestTune

varImp(xgb_tune5_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 766)

5.3.2.5 Fitting Final Model


(final_grid_2021 <- expand.grid(
  nrounds = xgb_tune5_2021$bestTune$nrounds,
  eta = xgb_tune5_2021$bestTune$eta,
  max_depth = xgb_tune5_2021$bestTune$max_depth,
  gamma = xgb_tune5_2021$bestTune$gamma,
  colsample_bytree = xgb_tune5_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune5_2021$bestTune$min_child_weight,
  subsample = xgb_tune5_2021$bestTune$subsample
))

(xgb_model_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = train_control,
  tuneGrid = final_grid_2021,
  method = "xgbTree",
  verbose = TRUE
))
eXtreme Gradient Boosting 

3197 samples
 766 predictor

No pre-processing
Resampling: None 
varImp(xgb_model_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 766)

5.4 Model Performance

5.4.1 Checking Model on Test Split Data

We don’t need to look too closely at are training data as Xgboost will heavily overfit the model based on that data. The more important part is how the model performs on in predicting our Test Sample that was not included.



y_pred_test <- predict(xgb_model_2021, data.matrix(te_treated_2021))

test_stats= cbind((te_treated_2021$Worth),y_pred_test)

test_statsR2 = cor(test_stats[,1],test_stats[,2])^2

print(test_statsR2)
[1] 0.79
y_pred_train <- predict(xgb_model_2021, data.matrix(tr_treated_2021))

train_stats = cbind((tr_treated_2021$Worth),y_pred_train)

train_statsR2 = cor(train_stats[,1],train_stats[,2])^2

print(train_statsR2)
[1] 0.94
#test dataset
x <- select(te_treated_2021, -Worth)
y <- (te_treated_2021$Worth)

(xgb_model_rmse <- ModelMetrics::rmse(y, predict(xgb_model_2021, newdata = x)))
[1] 0.33
holdout_x <- select(tr_treated_2021, -Worth)
holdout_y <- tr_treated_2021$Worth

(xgb_model_rmse <- ModelMetrics::rmse(holdout_y, predict(xgb_model_2021, newdata = holdout_x)))
[1] 0.18

5.4.1.1 Graphical Representation of Model


ggplot2::ggplot() +
  aes(x = test_stats[,1], y = test_stats[,2]) +
  geom_jitter() +
  xlab("Predicted Values") +
  ylab("Actual Values") +
  ggtitle("Results of Pitching Model on Test Data")+
  theme(plot.title = element_text(hjust = 0.5,size = 22,color ="steel blue"))+
  geom_smooth(method = "lm")
`geom_smooth()` using formula 'y ~ x'


6 Creating 2022 Projections from Model

6.1 Re-fit model for Important Variables

Now that we have an acceptable model, we can use it to create projections for how well we think players should do in 2022 based on their hitting statistics in 2021. First let’s reduce

Step 1: Only keep variables with high enough importance in model



vip(xgb_model_2021, num_features = 30)  # 10 is the default, 30 gives a visual on the top 30 most important features of the model


unscalevi = vi(xgb_model_2021, method="model") #shows the numbers behind the plot

unscalevi$Importance_perc = with(unscalevi,Importance/sum(Importance)) #adds percentages 

unscalevi # importance by variables

variables_to_keep_2021 = subset(unscalevi, Importance_perc > 0.0010) %>% select(Variable) #Keep Variables that explain at least a small amount [0.1%] of the model. This is a low threshold for inclusion ,but you can adjust this

variables_to_keep_2021b = t(variables_to_keep_2021)

variables_to_keep_2022 = colnames(row_to_names(variables_to_keep_2021b,row_number = 1))

tr_treated_2022 = tr_treated_2021 %>%  select(Worth,one_of(variables_to_keep_2022),starts_with("Team_lev_x_")) #keep modeled important variables along with team indicator variables

te_treated_2022 = te_treated_2021 %>%  select(Worth,one_of(variables_to_keep_2022),starts_with("Team_lev_x_"))

input_x_2022 = as.matrix(select(tr_treated_2022, -Worth))

input_y_2022 = tr_treated_2022$Worth

Step 2: Re-fit model with reduced variable scope
Note from the best tune below the nrounds - is the max I set above and eta is at the lowest possible value. This could cause potential overfitting issues, but from our Actual vs. Predicted Graph we know this not to be the case.



(final_grid_2021 <- expand.grid(
  nrounds = xgb_tune5_2021$bestTune$nrounds,
  eta = xgb_tune5_2021$bestTune$eta,
  max_depth = xgb_tune5_2021$bestTune$max_depth,
  gamma = xgb_tune5_2021$bestTune$gamma,
  colsample_bytree = xgb_tune5_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune5_2021$bestTune$min_child_weight,
  subsample = xgb_tune5_2021$bestTune$subsample
))

(xgb_model_2022 <- caret::train(
  x = input_x_2022,
  y = input_y_2022,
  trControl = train_control,
  tuneGrid = final_grid_2021,
  method = "xgbTree",
  verbose = TRUE
))
eXtreme Gradient Boosting 

3197 samples
  93 predictor

No pre-processing
Resampling: None 
vip(xgb_model_2022, num_features = 30)


unscalevi24 = vi(xgb_model_2022, method="model")

unscalevi24$Importance_perc = with(unscalevi24,Importance/sum(Importance)) 

unscalevi24

# Save work for later prediction

save(xgb_model_2022,file = '2022_Pitching6x6_Model.Rdata')

pitching6x6 = xgb_model_2022

pitchinginput6x6 = input_x_2022

6.2 Get 2022 list of players

6.2.1 Arrange the Data so the Columns are in the exact order as the model

First let’s prepare a file for predicting based on our model object



variableslag6x= row_to_names(as.data.frame(t(variables_to_keep_2022)),row_number = 1)  %>% select (starts_with("lag"))

variables_nolag6x = (owmr::remove_prefix(variableslag6x,"lag" , sep = "_"))

Data_Predict_2022a6x = total_treated_2021_pitching %>% select (one_of(colnames(variables_nolag6x)),Season,playerid)

colnames(Data_Predict_2022a6x) <- paste0("lag_", colnames(Data_Predict_2022a6x))

Data_Predict_2022b6x = total_treated_2021_pitching %>% select (one_of(colnames(variables_nolag6x)))
colnames(Data_Predict_2022b6x) = colnames(variableslag6x)

variables_to_keep_2022_nolag6x = total_treated_2021_pitching %>% select(one_of(variables_to_keep_2022),Season,playerid,starts_with("Team_lev_x_"))%>% select(-one_of(colnames(Data_Predict_2022b6x)))


Data_predict_20226x = sqldf(
  "
  select a.*,b.* from
  Data_Predict_2022a6x a,
  variables_to_keep_2022_nolag6x b
  on b.playerid = a.lag_playerid
  and b.Season = a.lag_Season
  "
) %>% select(-lag_playerid,lag_Season) %>%
  filter(Season == 2021) %>% 
  select(one_of(variables_to_keep_2022),starts_with("Team_lev_x_"))

6.3 Create Predictions for Model

6.3.1 Run Projections on Players who Played in 2021

This is the raw prediction score per IP for each pitcher


pitching_predictions6x = as.data.frame(predict(xgb_model_2022,Data_predict_20226x))

names(pitching_predictions6x) = c("Predict_Score")

Data_predict_2022_w_Pitching_Predictions6x = cbind(Data_predict_2022,pitching_predictions6x) %>% select(playerid,Predict_Score)

head(Data_predict_2022_w_Pitching_Predictions6x)
NA

6.3.2 Load in Latest 2022 Projections for Innings Pitched

Downloaded from FanGraphs here.

Latest_2022_pitchingdata_FP = read_csv("FanGraph_Fantasy_Baseball_Pitching.csv")
Rows: 817 Columns: 27
-- Column specification -------------------------------------------------------------------------
Delimiter: ","
chr  (3): Name, Team, playerid
dbl (24): GS, G, IP, W, L, QS, SV, HLD, H, ER, HR, SO, BB, WHIP, K/9, BB/9, ERA, FIP, WAR, RA...

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
Latest_2022_pitchingdata_FP
NA

As you can see from the chart below there aren’t many elite pitchers in the 87+ Predict score range.



Pitching_Data_NonAdj_Projections6x = sqldf(
  "
  select a.*,b.Predict_Score
  from Latest_2022_pitchingdata_FP a 
  left join 
  Data_predict_2022_w_Pitching_Predictions6x b
  on a.playerid = b.playerid
  "
) %>% filter(ADP<370 | is.na(Predict_Score)==F)


Pitching_Data_Adj_Projections6x =
Pitching_Data_NonAdj_Projections6x %>% 
  mutate(
    Avg_IP = 60,
    AdjPredict_Score_raw = ifelse(is.na(Predict_Score),NA,Predict_Score*(IP/Avg_IP)),
    max_predscore= max(AdjPredict_Score_raw,na.rm = T),
    AdjPredict_Score = ifelse (is.na(AdjPredict_Score_raw),NA,AdjPredict_Score_raw *100/max_predscore),
    WAR_rank = order(order(rank(WAR,ties.method = 'average'),decreasing = TRUE)),
    AdjPredict_Score_Rank = order(order(rank(AdjPredict_Score,ties.method = 'average'),decreasing = TRUE))-sum(is.na(AdjPredict_Score)),
        Ranks_Above_ADP = ADP - AdjPredict_Score_Rank
  ) %>% select (Name,ADP,WAR, WAR_rank,AdjPredict_Score ,AdjPredict_Score_Rank,Ranks_Above_ADP)


  

ggplot2::qplot(Pitching_Data_Adj_Projections6x$AdjPredict_Score, main="Predictions") + geom_histogram(colour="black", fill="grey") + theme_bw()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.


7 2022 Projections Full

7.1 Table of Pitching Projections (Players who Didn’t Play in 2021 - Recieve an NA)

AdjPredict_Score are normalized to 100


tableexport =
Pitching_Data_Adj_Projections6x %>%
  arrange (ADP,WAR) %>% 
  kbl() %>% 
 kable_material(c("striped", "hover","condensed","responsive"),full_width = F,fixed_thead = T)

save_kable(tableexport,file = "Pitching6x6.html")

#tableexport

This is a better formatted Table




ft_dt <- Pitching_Data_Adj_Projections6x[1:nrow(Pitching_Data_Adj_Projections6x), 1:ncol(Pitching_Data_Adj_Projections6x)] %>% 
  filter(AdjPredict_Score_Rank>0)%>%  arrange((AdjPredict_Score_Rank))

ft_dt$ADP <- color_tile("white", "red")(ft_dt$ADP)

ft_dt$WAR <- color_bar("lightblue")(ft_dt$WAR)

ft_dt$AdjPredict_Score<- color_bar("lightblue")(ft_dt$AdjPredict_Score)

ft_dt$WAR_Rank <- color_tile("green","orange")(ft_dt$WAR_rank)

ft_dt$Predict_Rank <- color_tile("green","orange")(ft_dt$AdjPredict_Score_Rank) 


ft_dt$Ranks_Above_ADP <- 
  ifelse(
  ft_dt$Ranks_Above_ADP < 0,
  cell_spec(round(ft_dt$Ranks_Above_ADP,2), color = "red", italic = T),
  cell_spec(round(ft_dt$Ranks_Above_ADP,2), color = "green", italic = T)
)


ft_dt2 <- ft_dt[c("Name", "ADP", "WAR", "AdjPredict_Score", "WAR_Rank","Predict_Rank","Ranks_Above_ADP")]



table_export = 
kbl(ft_dt2, escape = F) %>% 
 kable_material(c("striped", "hover","condensed","responsive"),full_width = F,fixed_thead = T) %>%   column_spec(6, width = "3cm") %>%
  add_header_above(c(" ", "Scores" = 3, "Ranks" = 2," "))

save_kable(table_export,file = "Pitching6x6_updated.html")
  
table_export  
Scores
Ranks
Name ADP WAR AdjPredict_Score WAR_Rank Predict_Rank Ranks_Above_ADP
Eduardo Rodriguez 150.1 3.5 100.0 15 1 149.1
Brady Singer 492.4 2.2 97.1 53 2 490.4
Dylan Cease 81.2 3.3 95.4 20 3 78.2
Josiah Gray 290.7 1.6 93.1 89 4 286.7
José Quintana 575.5 1.0 92.8 143 5 570.5
Shane Bieber 31.2 4.4 91.9 4 6 25.2
Tarik Skubal 197.6 2.0 90.9 61 7 190.6
Aaron Nola 41.0 4.2 88.3 7 8 33
Nick Pivetta 350.8 1.7 87.5 86 9 341.8
Alek Manoah 93.0 2.8 87.5 33 10 83
Shane McClanahan 109.6 2.6 87.3 40 11 98.6
Luis Castillo 103.2 3.3 85.4 21 12 91.2
Zac Gallen 154.3 2.3 85.2 50 13 141.3
Logan Webb 69.9 3.7 84.7 11 14 55.9
Tyler Mahle 132.2 2.6 84.7 39 15 117.2
Logan Gilbert 161.8 2.2 84.6 52 16 145.8
Corbin Burnes 10.8 5.3 84.5 1 17 -6.2
Jon Gray 236.9 2.3 84.4 51 18 218.9
Brad Keller 582.1 1.6 83.8 91 19 563.1
Germán Márquez 261.7 2.9 83.5 29 20 241.7
Walker Buehler 18.6 3.8 80.5 8 21 -2.4
Yusei Kikuchi 301.3 1.7 80.2 83 22 279.3
Jesús Luzardo 289.5 0.8 79.8 158 23 266.5
Gerrit Cole 7.1 5.0 79.0 3 24 -16.9
Luis Garcia 144.7 2.0 78.9 64 25 119.7
Sean Manaea 141.4 2.8 78.9 31 26 115.4
Freddy Peralta 54.0 3.6 78.8 12 27 27
Trevor Rogers 92.9 3.1 78.7 25 28 64.9
Lucas Giolito 44.2 3.7 77.9 10 29 15.2
Michael Kopech 161.1 2.5 77.9 43 30 131.1
Ian Anderson 151.1 2.1 77.0 56 31 120.1
Tanner Houck 206.4 2.4 76.5 47 32 174.4
José Berríos 77.5 3.2 76.4 23 33 44.5
Andrew Heaney 299.4 1.4 76.3 102 34 265.4
Framber Valdez 149.6 3.1 76.3 26 35 114.6
Luis Patiño 290.7 1.5 75.5 99 36 254.7
Patrick Sandoval 196.0 2.4 75.4 46 37 159
Frankie Montas 92.8 3.3 75.2 19 38 54.8
Cole Irvin 532.2 1.5 74.8 97 39 493.2
Kris Bubic 536.0 1.1 73.7 125 40 496
Nathan Eovaldi 135.1 3.6 73.5 13 41 94.1
Julio Urías 34.7 3.5 72.8 14 42 -7.3
Dane Dunning 484.6 1.9 72.4 72 43 441.6
Jordan Montgomery 208.7 2.7 72.4 35 44 164.7
Patrick Corbin 456.0 1.6 72.2 95 45 411
Steven Matz 249.2 1.9 72.2 67 46 203.2
Blake Snell 113.4 2.4 71.9 49 47 66.4
Zac Lowther 999.0 0.8 71.9 159 48 951
Aaron Civale 269.5 1.5 71.8 96 49 220.5
Zach Plesac 346.6 1.5 71.6 100 50 296.6
Bruce Zimmermann 600.8 1.3 71.6 114 51 549.8
Carlos Hernández 412.5 1.2 71.5 121 52 360.5
Mitch Keller 531.5 1.2 71.5 123 53 478.5
Kyle Hendricks 279.8 1.7 70.6 79 54 225.8
Cristian Javier 284.0 1.2 70.6 118 55 229
Sandy Alcantara 43.4 3.3 70.4 18 56 -12.6
Bailey Ober 261.3 2.2 70.2 55 57 204.3
Shohei Ohtani 9.2 2.8 70.1 32 58 -48.8
Kevin Gausman 71.4 3.2 69.9 24 59 12.4
Kyle Gibson 393.9 1.9 69.4 71 60 333.9
Robbie Ray 49.9 3.0 69.4 27 61 -11.1
Michael Lorenzen 543.3 0.6 69.3 203 62 481.3
Yu Darvish 100.3 2.7 69.3 34 63 37.3
Matt Manning 543.4 1.1 69.2 124 64 479.4
Sonny Gray 168.2 2.7 68.8 38 65 103.2
Joe Ryan 221.5 2.2 68.5 54 66 155.5
Huascar Ynoa 245.8 1.8 68.0 76 67 178.8
Mike Minor 532.2 2.0 67.8 63 68 464.2
Brandon Woodruff 21.2 4.2 67.2 6 69 -47.8
Triston McKenzie 229.0 1.6 67.2 93 70 159
JT Brubaker 537.7 1.4 66.6 105 71 466.7
Hyun Jin Ryu 207.3 2.4 66.3 45 72 135.3
Dylan Bundy 498.5 1.3 66.2 109 73 425.5
Kyle Freeland 574.4 1.5 66.0 98 74 500.4
Max Fried 69.8 3.5 65.9 16 75 -5.2
Keegan Akin 600.0 1.0 65.9 139 76 524
Chris Flexen 413.6 1.9 65.3 73 77 336.6
Casey Mize 279.4 1.7 65.0 84 78 201.4
Ranger Suárez 179.3 2.4 64.9 44 79 100.3
Joe Musgrove 70.6 3.2 64.7 22 80 -9.4
Spencer Howard 575.8 0.9 64.4 150 81 494.8
Reid Detmers 427.4 1.1 64.2 134 82 345.4
Drew Rasmussen 282.4 1.6 63.5 88 83 199.4
Antonio Senzatela 585.0 2.0 63.1 62 84 501
Taylor Hearn 591.2 0.7 62.7 183 85 506.2
Dallas Keuchel 567.3 1.2 62.7 119 86 481.3
Austin Gomber 551.9 1.2 62.4 116 87 464.9
Pablo López 140.6 2.7 62.3 36 88 52.6
Max Scherzer 19.7 3.8 62.1 9 89 -69.3
Jordan Lyles 578.9 0.6 61.5 196 90 488.9
Ryan Yarbrough 541.5 1.4 61.5 106 91 450.5
Cal Quantrill 267.3 1.7 61.0 82 92 175.3
Merrill Kelly 526.0 1.7 60.8 81 93 433
José Suarez 535.1 1.3 60.6 113 94 441.1
Carlos Carrasco 284.6 1.3 60.3 110 95 189.6
Zack Wheeler 31.0 4.4 60.3 5 96 -65
Alex Wood 219.9 1.7 59.8 78 97 122.9
Vladimir Gutierrez 585.4 0.2 59.7 322 98 487.4
Carlos Rodón 110.2 2.9 59.5 28 99 11.2
Eric Lauer 306.3 1.4 59.5 103 100 206.3
Chris Bassitt 128.5 2.4 59.1 48 101 27.5
Aaron Ashby 264.1 1.5 58.9 101 102 162.1
Lance Lynn 70.0 3.4 58.9 17 103 -33
Alex Cobb 242.9 1.9 58.8 70 104 138.9
Tony Gonsolin 300.6 0.9 58.7 148 105 195.6
Elieser Hernandez 339.6 1.0 58.4 145 106 233.6
José Urquidy 212.1 2.0 58.2 65 107 105.1
James Kaprielian 448.3 1.1 58.2 126 108 340.3
Wil Crowe 600.8 0.1 57.0 379 109 491.8
Wade Miley 502.0 1.2 56.9 120 110 392
Chris Sale 85.9 1.9 56.8 66 111 -25.1
Charlie Morton 94.3 2.9 56.3 30 112 -17.7
Bryse Wilson 600.5 0.5 56.3 223 113 487.5
Zack Greinke 312.8 1.6 56.1 90 114 198.8
Marco Gonzales 319.6 1.7 56.0 80 115 204.6
Chris Paddack 436.8 1.2 55.9 117 116 320.8
Zach Davies 595.9 0.2 55.7 343 117 478.9
J.A. Happ 597.0 0.6 55.2 199 118 479
Erick Fedde 589.7 0.6 54.9 188 119 470.7
Daniel Bard 579.3 0.4 54.8 266 120 459.3
Marcus Stroman 187.5 2.6 54.7 41 121 66.5
Zach Eflin 485.0 1.9 54.3 69 122 363
Tylor Megill 334.2 1.1 54.0 129 123 211.2
Martín Pérez 598.8 0.8 53.8 168 124 474.8
Jack Flaherty 115.4 1.6 53.7 87 125 -9.6
Michael Wacha 549.7 0.9 53.6 149 126 423.7
Adam Ottavino 598.1 0.2 53.3 328 127 471.1
Alec Mills 594.1 0.6 53.3 211 128 466.1
Daniel Lynch 559.5 0.7 52.7 174 129 430.5
Anthony DeSclafani 212.6 1.8 52.7 77 130 82.6
Jameson Taillon 299.6 2.1 52.7 59 131 168.6
Corey Kluber 324.4 1.1 52.4 130 132 192.4
Luke Weaver 514.4 1.1 52.1 128 133 381.4
Garrett Whitlock 239.2 1.2 52.0 122 134 105.2
José Alvarado 587.6 0.5 51.8 237 135 452.6
John Means 217.1 2.7 50.8 37 136 81.1
Tanner Rainey 371.9 0.2 50.6 325 137 234.9
Gregory Soto 194.3 0.5 50.5 228 138 56.3
Drew Smyly 577.4 0.6 50.4 208 139 438.4
Héctor Neris 493.9 0.4 50.0 242 140 353.9
Glenn Otto 582.9 0.9 50.0 152 141 441.9
Taijuan Walker 414.5 0.6 49.6 185 142 272.5
James Karinchak 468.6 0.7 49.5 171 143 325.6
Randy Dobnak 600.4 0.8 49.4 162 144 456.4
Madison Bumgarner 507.1 1.1 49.4 131 145 362.1
Nestor Cortes 333.1 1.4 49.3 107 146 187.1
Jacob deGrom 19.7 5.1 49.0 2 147 -127.3
Adrian Houser 465.0 1.4 49.0 104 148 317
Kyle Finnegan 351.5 0.2 48.5 336 149 202.5
Edward Cabrera 523.6 0.6 47.8 202 150 373.6
Paul Sewald 289.0 0.5 47.7 234 151 138
Chad Green 403.2 1.0 47.7 147 152 251.2
Devin Williams 287.1 1.1 47.6 133 153 134.1
Scott Barlow 159.3 0.9 47.5 155 154 5.3
Clayton Kershaw 149.4 2.5 47.4 42 155 -5.6
Pete Fairbanks 517.3 0.6 47.4 194 156 361.3
Jake Diekman 585.9 0.5 47.2 232 157 428.9
Amir Garrett 586.6 0.2 47.1 333 158 428.6
Carlos Estévez 528.6 0.2 47.0 337 159 369.6
Phil Maton 600.4 0.4 46.6 248 160 440.4
Stephen Strasburg 294.0 1.3 46.5 111 161 133
Tanner Scott 599.0 0.7 46.4 182 162 437
Lucas Sims 243.6 0.7 46.1 180 163 80.6
Tyler Alexander 586.8 0.8 45.9 167 164 422.8
Trevor May 565.3 0.4 45.9 239 165 400.3
Ryne Stanek 598.8 0.2 45.8 330 166 432.8
Alex Colomé 378.0 0.1 45.8 387 167 211
Adam Wainwright 194.7 2.1 45.8 58 168 26.7
Kyle Funkhouser 999.0 0.1 45.8 386 169 830
A.J. Alexy 596.2 0.6 45.7 186 170 426.2
Aroldis Chapman 83.3 0.9 45.6 151 171 -87.7
Justin Steele 598.7 0.4 45.6 245 172 426.7
Aaron Bummer 595.4 1.0 45.4 137 173 422.4
Josh Fleming 600.8 0.5 45.4 222 174 426.8
Brad Boxberger 588.3 0.2 45.4 331 175 413.3
Mychal Givens 549.7 0.1 45.2 419 176 373.7
Zach Thompson 546.8 1.0 45.2 146 177 369.8
Bryan Shaw 597.8 -0.1 45.1 507 178 419.8
Hansel Robles 596.0 -0.1 45.1 505 179 417
Blake Treinen 147.6 0.8 44.9 166 180 -32.4
Mike Mayers 594.7 0.5 44.6 221 181 413.7
Chris Stratton 519.4 0.3 44.3 284 182 337.4
Giovanny Gallegos 113.5 1.0 44.3 140 183 -69.5
Miles Mikolas 512.8 1.6 43.8 94 184 328.8
Brent Suter 587.6 0.4 43.8 268 185 402.6
Garrett Crochet 549.8 0.9 43.6 154 186 363.8
Rich Hill 477.4 0.7 43.6 173 187 290.4
Dillon Peters 600.7 0.0 43.4 421 188 412.7
Joe Jiménez 999.0 0.2 43.3 329 189 810
Lou Trivino 237.2 0.2 43.2 316 190 47.2
Domingo Germán 494.6 1.1 43.1 132 191 303.6
Daniel Norris 999.0 0.1 43.1 398 192 807
Génesis Cabrera 596.6 0.6 43.1 205 193 403.6
Tyler Wells 454.6 0.9 43.1 153 194 260.6
David Peterson 595.2 0.7 43.0 175 195 400.2
Heath Hembree 600.8 0.2 42.9 338 196 404.8
Joely Rodríguez 600.9 0.5 42.8 214 197 403.9
Tyler Anderson 564.5 0.7 42.7 172 198 366.5
Matt Barnes 245.2 0.8 42.7 164 199 46.2
Jonathan Loáisiga 393.7 1.0 42.6 142 200 193.7
Johnny Cueto 585.8 0.3 42.5 283 201 384.8
Seth Lugo 598.1 0.5 42.5 236 202 396.1
Yimi García 581.1 0.2 42.4 352 203 378.1
José Cisnero 999.0 0.3 42.2 281 204 795
Austin Voth 600.4 0.0 42.1 420 205 395.4
Michael Fulmer 347.0 0.6 42.1 192 206 141
Rowan Wick 302.0 0.4 42.0 249 207 95
Camilo Doval 159.8 0.5 41.9 229 208 -48.2
Cole Sulser 427.2 0.7 41.6 177 209 218.2
Justus Sheffield 599.5 0.4 41.4 269 210 389.5
J.B. Wendelken 600.1 0.2 41.3 341 211 389.1
Jordan Romano 88.3 0.8 41.2 160 212 -123.7
David Price 581.5 0.5 41.2 216 213 368.5
Matt Wisler 599.6 0.4 41.1 270 214 385.6
Diego Castillo 409.2 0.6 41.0 191 215 194.2
Justin Dunn 577.6 0.6 40.6 198 216 361.6
Hirokazu Sawamura 589.6 0.0 40.6 443 217 372.6
Brad Hand 520.0 0.2 40.2 351 218 302
Emilio Pagán 551.7 0.1 40.1 364 219 332.7
Jaime Barría 999.0 0.5 40.1 238 220 779
Anthony Banda 999.0 -0.1 40.1 514 221 778
Anthony Bender 412.8 0.7 40.1 184 222 190.8
Edwin Díaz 63.5 1.1 39.9 135 223 -159.5
Michael Pineda 465.2 1.4 39.9 108 224 241.2
Tyler Rogers 534.3 0.5 39.8 230 225 309.3
Sam Hentges 999.0 0.3 39.8 306 226 773
Jeurys Familia 597.5 0.3 39.8 305 227 370.5
Griffin Canning 594.2 0.5 39.8 213 228 366.2
Lance McCullers Jr. 260.0 1.7 39.8 85 229 31
Daniel Hudson 550.2 0.5 39.7 225 230 320.2
Clay Holmes 600.3 0.6 39.7 201 231 369.3
Andrew Wantz 999.0 0.4 39.7 262 232 767
Taylor Rogers 172.5 1.1 39.6 127 233 -60.5
Caleb Smith 592.3 0.0 39.6 441 234 358.3
Jorge Alcala 459.0 0.6 39.6 209 235 224
Luis Gil 525.9 0.8 39.5 165 236 289.9
Dylan Floro 221.3 0.4 39.5 251 237 -15.7
Josh Staumont 505.8 0.4 39.2 244 238 267.8
Spencer Patton 599.0 0.1 39.1 410 239 360
Tyler Duffey 577.8 0.4 39.0 260 240 337.8
Jake Brentz 600.8 0.3 39.0 301 241 359.8
Josh Sborz 600.9 0.3 38.9 290 242 358.9
Raisel Iglesias 47.6 1.0 38.8 138 243 -195.4
Chad Kuhl 600.7 0.2 38.8 349 244 356.7
Ryan Pressly 64.3 1.0 38.8 136 245 -180.7
J.P. Feyereisen 597.6 0.0 38.7 487 246 351.6
Keegan Thompson 999.0 0.0 38.3 429 247 752
J.C. Mejía 999.0 0.1 38.2 389 248 751
Liam Hendriks 32.0 1.6 38.1 92 249 -217
Sam Howard 999.0 0.3 38.1 310 250 749
Robert Stephenson 590.5 0.0 38.0 466 251 339.5
Tim Mayza 599.3 0.6 37.9 197 252 347.3
José Ureña 999.0 0.0 37.8 467 253 746
Craig Kimbrel 164.4 0.7 37.6 176 254 -89.6
Jeff Hoffman 999.0 0.0 37.5 426 255 744
Garrett Richards 598.7 0.4 37.4 272 256 342.7
Anthony Misiewicz 999.0 0.3 37.3 295 257 742
Tony Santillan 600.3 0.4 37.3 263 258 342.3
Brusdar Graterol 567.4 0.7 37.2 178 259 308.4
Andrew Kittredge 257.6 0.8 37.2 169 260 -2.4
A.J. Minter 596.5 0.5 37.1 215 261 335.5
Trevor Stephan 999.0 0.1 37.1 368 262 737
Ryan Helsley 600.2 0.1 37.0 380 263 337.2
Ryan Tepera 581.3 0.4 36.8 246 264 317.3
David Bednar 190.8 0.9 36.8 156 265 -74.2
Jackson Kowar 594.6 0.5 36.8 233 266 328.6
Dean Kremer 999.0 0.6 36.7 210 267 732
Jake Odorizzi 533.5 0.8 36.6 163 268 265.5
Patrick Murphy 999.0 0.4 36.6 254 269 730
Yusmeiro Petit 601.0 -0.3 36.5 531 270 331
Sergio Romo 600.5 0.0 36.4 450 271 329.5
Tyler Kinley 999.0 0.0 36.3 433 272 727
Kendall Graveman 531.0 0.4 36.3 240 273 258
Griffin Jax 600.8 0.1 36.3 363 274 326.8
Trevor Richards 600.5 0.4 36.3 261 275 325.5
Rafael Dolis 999.0 0.0 36.2 434 276 723
Sam Coonrod 600.9 0.4 36.1 271 277 323.9
Emmanuel Clase 57.5 1.3 36.0 112 278 -220.5
Caleb Thielbar 999.0 0.6 36.0 195 279 720
Dinelson Lamet 400.5 0.9 36.0 157 280 120.5
Tyler Matzek 573.2 0.7 36.0 181 281 292.2
Corey Knebel 151.3 0.6 35.9 204 282 -130.7
Josh Taylor 999.0 0.5 35.4 217 283 716
Paolo Espino 599.2 0.2 35.2 327 284 315.2
Nick Wittgren 999.0 0.1 35.2 374 285 714
Tim Hill 999.0 0.3 35.1 293 286 713
Wandy Peralta 999.0 0.2 35.1 340 287 712
Will Smith 120.8 0.5 35.1 227 288 -167.2
Anthony Bass 600.9 0.1 35.1 412 289 311.9
Alex Vesia 600.0 0.3 35.0 289 290 310
Josh Hader 30.5 1.3 35.0 115 291 -260.5
Archie Bradley 600.7 0.2 35.0 348 292 308.7
Eli Morgan 600.5 0.2 34.6 346 293 307.5
JT Chargois 600.3 0.3 34.5 279 294 306.3
Pierce Johnson 433.3 0.6 34.4 200 295 138.3
Duane Underwood Jr. 999.0 0.1 34.4 417 296 703
Ross Stripling 583.7 0.4 34.3 256 297 286.7
Brooks Raley 600.9 0.3 34.0 296 298 302.9
Miguel Castro 600.9 0.1 33.8 372 299 301.9
Lucas Gilbreath 596.4 0.0 33.7 471 300 296.4
José Quijada 999.0 0.5 33.7 212 301 698
Connor Brogdon 600.4 0.4 33.6 252 302 298.4
Nick Sandlin 599.8 0.6 33.6 207 303 296.8
Jhoulys Chacín 999.0 -0.4 33.5 533 304 695
Jorge López 599.9 0.4 33.4 265 305 294.9
Dillon Tate 600.9 0.4 33.3 264 306 294.9
Jake Cousins 600.1 0.5 33.3 226 307 293.1
Luke Jackson 598.1 0.4 33.1 267 308 290.1
Adbert Alzolay 444.0 0.7 33.0 179 309 135
Kenley Jansen 90.0 0.6 33.0 187 310 -220
Jarlín García 600.8 0.1 32.9 411 311 289.8
Darwinzon Hernandez 999.0 0.2 32.7 324 312 687
Trevor Williams 601.0 0.2 32.5 326 313 288
Rafael Montero 999.0 0.1 32.5 394 314 685
Drew Steckenrider 401.5 0.3 32.5 294 315 86.5
Austin Warren 999.0 0.5 32.4 231 316 683
Matthew Boyd 591.0 1.0 32.3 144 317 274
Collin McHugh 545.5 0.6 32.3 189 318 227.5
John King 999.0 0.5 32.1 219 319 680
Vince Velasquez 599.4 0.3 32.1 308 320 279.4
Alex Lange 999.0 0.1 32.0 366 321 678
Justin Wilson 999.0 0.1 32.0 377 322 677
Mark Melancon 131.8 0.4 31.9 247 323 -191.2
Tyler Gilbert 599.9 0.4 31.9 255 324 275.9
Zach Pop 999.0 0.2 31.9 345 325 674
Sean Doolittle 598.5 0.1 31.8 402 326 272.5
Bailey Falter 599.8 0.5 31.8 218 327 272.8
Zack Littell 599.3 0.1 31.6 367 328 271.3
Kyle Zimmer 999.0 -0.2 31.3 528 329 670
Steve Cishek 595.3 0.0 31.3 439 330 265.3
Ian Kennedy 409.8 -0.1 31.2 512 331 78.8
Jake Woodford 599.5 0.0 31.1 460 332 267.5
Kwang Hyun Kim 593.0 0.3 31.1 303 333 260
Joe Smith 999.0 0.1 31.0 361 334 665
Ryan Hendrix 999.0 0.0 31.0 468 335 664
Cionel Pérez 999.0 0.5 31.0 235 336 663
Jeffrey Springs 999.0 0.3 31.0 287 337 662
Brett Martin 999.0 0.4 30.9 275 338 661
Max Kranick 999.0 0.3 30.8 297 339 660
Jakob Junis 600.1 0.2 30.7 332 340 260.1
Adam Morgan 999.0 0.1 30.3 391 341 658
Ryan Thompson 999.0 0.3 30.3 278 342 657
Taylor Widener 600.5 0.0 30.2 424 343 257.5
Jake McGee 251.1 0.4 30.1 241 344 -92.9
Andrew Chafin 587.9 0.6 29.9 206 345 242.9
Blake Taylor 999.0 0.2 29.8 353 346 653
Dominic Leone 600.8 0.1 29.7 371 347 253.8
Phil Bickford 600.9 0.4 29.6 258 348 252.9
Johan Oviedo 999.0 0.2 29.6 356 349 650
Chris Martin 600.3 0.4 29.6 277 350 250.3
Dennis Santana 999.0 0.0 29.4 442 351 648
Craig Stammen 599.4 0.4 29.4 259 352 247.4
Tony Watson 999.0 0.0 28.7 479 353 646
Taylor Clarke 999.0 0.0 28.6 437 354 645
Luis Cessa 588.8 0.3 28.5 299 355 233.8
Austin Adams 600.0 0.4 28.3 273 356 244
Joe Barlow 216.1 0.2 28.3 344 357 -140.9
Brett Anderson 600.9 0.6 28.2 190 358 242.9
Joe Kelly 600.4 0.4 28.2 243 359 241.4
Reynaldo López 557.4 0.4 28.2 257 360 197.4
Sean Newcomb 600.1 0.1 28.1 400 361 239.1
Austin Davis 999.0 0.2 28.1 320 362 637
Mike Foltynewicz 600.4 -0.1 28.0 511 363 237.4
Sammy Long 596.1 0.3 27.9 307 364 232.1
Deolis Guerra 599.9 0.2 27.9 342 365 234.9
Albert Abreu 999.0 -0.1 27.9 496 366 633
Josh Tomlin 999.0 -0.3 27.8 532 367 632
Aaron Loup 594.4 0.4 27.8 274 368 226.4
Bryan Garcia 999.0 -0.2 27.7 530 369 630
Joe Mantiply 999.0 0.2 27.6 334 370 629
Greg Holland 600.2 0.0 27.5 473 371 229.2
Tucker Davidson 584.9 0.6 27.4 193 372 212.9
Joe Ross 599.6 0.7 27.3 170 373 226.6
Alex Reyes 395.7 0.2 27.2 335 374 21.7
Alexander Wells 999.0 0.4 26.8 276 375 624
Wander Suero 999.0 0.2 26.7 317 376 623
Demarcus Evans 600.6 0.0 26.7 472 377 223.6
Michael King 600.5 0.5 26.6 220 378 222.5
Richard Rodríguez 556.5 0.1 26.5 414 379 177.5
Richard Bleier 600.7 0.5 26.4 224 380 220.7
Ryan Weathers 600.8 0.3 26.4 300 381 219.8
Yency Almonte 999.0 -0.2 26.3 524 382 617
Matt Harvey 999.0 0.3 26.1 286 383 616
Logan Allen 600.8 0.1 25.9 418 384 216.8
Art Warren 482.3 0.8 25.9 161 385 97.3
Ralph Garza Jr. 999.0 -0.1 25.9 501 386 613
Andrew Miller 999.0 -0.1 25.8 510 387 612
Brad Brach 999.0 0.0 25.4 436 388 611
Nick Mears 999.0 0.0 25.4 480 389 610
Sean Reid-Foley 999.0 0.0 25.3 438 390 609
Domingo Tapia 999.0 0.0 25.0 492 391 608
Adam Cimber 600.6 0.3 24.9 282 392 208.6
Sean Poppen 999.0 0.2 24.8 319 393 606
Kolby Allard 601.0 0.2 24.6 312 394 207
Hunter Strickland 599.9 0.0 24.6 423 395 204.9
Brandon Kintzler 999.0 0.0 24.5 464 396 603
Matt Shoemaker 999.0 0.0 24.5 457 397 602
Ross Detwiler 999.0 0.0 24.4 474 398 601
Steven Brault 600.9 0.0 24.2 432 399 201.9
Steven Okert 999.0 0.3 24.2 304 400 599
Josh Rogers 600.8 -0.1 24.1 506 401 199.8
Noé Ramirez 600.9 0.1 24.1 390 402 198.9
Jesse Chavez 999.0 0.1 24.0 408 403 596
Wily Peralta 600.8 0.1 23.9 388 404 196.8
Tyler Clippard 600.8 -0.2 23.8 529 405 195.8
Jacob Webb 999.0 0.0 23.7 447 406 593
Drew Smith 999.0 0.1 23.4 360 407 592
Chi Chi González 999.0 0.1 23.3 396 408 591
Erik Swanson 600.1 0.3 23.0 291 409 191.1
Packy Naughton 999.0 0.3 23.0 292 410 589
Andres Machado 999.0 -0.1 22.8 513 411 588
Derek Holland 999.0 -0.1 22.6 498 412 587
Jay Jackson 999.0 0.2 22.6 347 413 586
T.J. McFarland 999.0 0.1 22.4 378 414 585
Mitch White 562.7 0.3 22.3 288 415 147.7
Buck Farmer 999.0 -0.2 22.2 519 416 583
Blake Parker 999.0 0.1 22.1 393 417 582
Junior Guerra 999.0 0.0 22.0 490 418 581
Touki Toussaint 598.6 0.1 22.0 369 419 179.6
Ryan Borucki 999.0 0.2 21.9 355 420 579
Trevor Bauer 208.4 1.0 21.6 141 421 -212.6
Jharel Cotton 600.8 0.1 21.6 376 422 178.8
Kodi Whitley 999.0 0.2 21.6 350 423 576
Bryan Abreu 999.0 0.1 21.5 392 424 575
Charlie Barnes 999.0 0.2 21.5 339 425 574
Michael Rucker 999.0 0.0 21.5 427 426 573
Ben Bowden 999.0 0.1 21.4 395 427 572
Kyle Muller 590.8 0.3 21.3 302 428 162.8
Nabil Crismatt 999.0 0.1 21.1 403 429 570
Brandon Bielak 999.0 0.1 21.0 362 430 569
Cody Ponce 999.0 0.2 20.8 354 431 568
Mason Thompson 999.0 0.0 20.8 448 432 567
Trevor Cahill 999.0 0.2 20.0 313 433 566
Alex Claudio 999.0 0.0 19.9 463 434 565
Carlos Martínez 596.0 0.0 19.8 477 435 161
Ryan Burr 999.0 0.0 19.7 440 436 563
Paul Campbell 999.0 -0.1 19.6 495 437 562
Brandon Workman 999.0 -0.2 19.6 521 438 561
Jake Arrieta 999.0 0.0 19.5 481 439 560
Joel Payamps 999.0 0.3 19.0 285 440 559
Trent Thornton 999.0 0.1 18.9 401 441 558
Jordan Holloway 999.0 -0.1 18.8 502 442 557
Juan Minaya 999.0 0.1 18.6 397 443 556
Casey Sadler 600.5 0.3 18.6 280 444 156.5
Kyle McGowin 999.0 0.2 18.4 321 445 554
Matt Foster 999.0 0.1 18.3 373 446 553
Humberto Castellanos 999.0 0.1 18.2 370 447 552
Paul Blackburn 999.0 0.2 18.0 315 448 551
José Álvarez 600.2 0.2 17.7 359 449 151.2
Tyler Zuber 999.0 0.0 17.6 462 450 549
Yohan Ramirez 599.8 0.1 17.4 413 451 148.8
Jordan Sheffield 999.0 -0.2 17.4 520 452 547
Miguel Sánchez 999.0 0.0 17.3 484 453 546
Jacob Barnes 999.0 0.1 17.1 381 454 545
Justin Garza 999.0 -0.2 16.7 523 455 544
Drew Pomeranz 582.3 0.1 16.7 383 456 126.3
Phillips Valdez 999.0 -0.1 16.4 493 457 542
Ryne Harper 999.0 0.1 16.3 416 458 541
Danny Coulombe 999.0 0.1 16.2 382 459 540
José Ruiz 999.0 0.2 16.1 357 460 539
JD Hammer 999.0 0.0 16.0 483 461 538
Michael Feliz 999.0 0.0 15.9 425 462 537
Ervin Santana 999.0 -0.5 15.8 534 463 536
Tommy Nance 999.0 0.1 15.6 385 464 535
Rex Brothers 999.0 0.0 15.6 461 465 534
Junior Fernández 999.0 0.1 15.5 415 466 533
Anthony Castro 999.0 0.1 15.2 375 467 532
Dan Winkler 999.0 -0.1 15.1 515 468 531
Chasen Shreve 999.0 -0.1 15.0 509 469 530
Héctor Santiago 999.0 -0.2 14.7 525 470 529
Brett de Geus 999.0 0.0 14.6 475 471 528
Hoby Milner 999.0 0.1 14.3 405 472 527
Wade LeBlanc 999.0 0.2 14.1 358 473 526
Matt Peacock 999.0 0.0 14.1 488 474 525
Shane Greene 999.0 -0.1 13.7 499 475 524
Nick Neidert 999.0 -0.2 13.7 517 476 523
Aaron Sanchez 600.6 0.0 13.6 451 477 123.6
Matt Moore 999.0 -0.1 13.5 503 478 521
Anthony Kay 999.0 0.0 13.4 422 479 520
Sean Guenther 999.0 0.2 13.4 323 480 519
Victor González 999.0 0.1 13.3 365 481 518
Keynan Middleton 999.0 -0.1 13.1 500 482 517
Humberto Mejía 999.0 0.0 13.1 453 483 516
Sam Selman 999.0 0.0 12.9 444 484 515
Danny Duffy 587.9 0.2 12.3 318 485 102.9
Cody Poteet 999.0 0.1 12.1 407 486 513
Chris Mazza 999.0 0.3 11.9 309 487 512
Kohei Arihara 999.0 0.0 11.9 449 488 511
Yennsy Díaz 999.0 -0.1 11.9 516 489 510
Braxton Garrett 999.0 0.0 11.4 428 490 509
Louis Head 600.5 0.3 11.4 311 491 109.5
Ashton Goudeau 999.0 -0.2 11.4 527 492 507
Chase Anderson 999.0 0.0 11.2 491 493 506
Cam Bedrosian 999.0 0.0 10.7 458 494 505
César Valdez 600.3 0.0 10.6 431 495 105.3
Sam Clay 999.0 0.0 10.3 430 496 503
Dustin May 559.5 0.3 10.1 298 497 62.5
Daniel Ponce de Leon 600.7 0.0 9.6 456 498 102.7
Dillon Maples 999.0 0.1 9.3 409 499 500
Erasmo Ramírez 999.0 -0.2 9.2 526 500 499
Caleb Baragar 999.0 -0.2 9.2 522 501 498
Robert Gsellman 999.0 -0.2 8.6 518 502 497
Marcos Diplán 999.0 0.0 8.6 459 503 496
Miguel Diaz 999.0 -0.1 8.5 494 504 495
Edwin Uceta 999.0 0.0 8.1 455 505 494
Enyel De Los Santos 999.0 0.1 7.6 404 506 493
Wes Benjamin 999.0 -0.1 7.4 504 507 492
Thomas Eshelman 999.0 0.0 7.3 476 508 491
Reiss Knehr 999.0 0.0 7.3 446 509 490
Jake Faria 999.0 0.0 6.9 469 510 489
Luke Farrell 999.0 0.0 6.8 489 511 488
Tayler Saucedo 999.0 0.1 6.7 399 512 487
Robert Dugger 999.0 0.0 6.2 478 513 486
Shaun Anderson 999.0 0.0 6.2 445 514 485
Adrian Sampson 999.0 -0.1 6.1 508 515 484
Drew Hutchison 999.0 -0.1 5.7 497 516 483
Kyle Crick 999.0 0.0 5.5 465 517 482
Shawn Armstrong 999.0 0.0 5.4 454 518 481
Edgar Santana 999.0 0.0 5.3 452 519 480
Kevin Ginkel 999.0 0.0 5.1 470 520 479
Sean Nolin 999.0 0.0 5.0 485 521 478
Carson Fulmer 999.0 0.0 4.8 482 522 477
Tyler Glasnow 598.8 0.2 4.6 314 523 75.8
Daniel Castano 999.0 0.0 4.0 486 524 475
John Gant 600.8 0.0 4.0 435 525 75.8
Kenta Maeda 600.7 0.1 3.3 384 526 74.7
Chris Ellis 600.8 0.1 3.0 406 527 73.8
NA
NA
NA
NA
NA
NA

LS0tDQp0aXRsZTogIldlbGNvbWUgdG8gbXkgMjAyMiBQcm9qZWN0aW9ucyBmb3IgUGl0Y2hlcnMgNng2Ig0KYXV0aG9yOiAiRGFyc2hhbiBQYXRlbCINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgdGhlbWU6IHNhbmRzdG9uZQ0KICAgIGhpZ2hsaWdodDogdGFuZ28NCiAgICBmaWdfY2FwdGlvbjogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KLS0tDQoNCg0KPGh0bWw+DQoNCjxwPg0KDQpQcm9qZWN0aW9ucyB1c2luZyBIeXBlcnR1bmVkIG1vZGVsIHRocm91Z2ggWEdib29zdA0KDQo8L3A+DQoNCjxwPg0KDQpBbGwgZGF0YSBpcyBmcm9tIFtGYW5HcmFwaHMuXShodHRwczovL3d3dy5mYW5ncmFwaHMuY29tLykgSSBoYXZlIG5vIGFmZmlsaWF0aW9uIHdpdGggRmFuR3JhcGhzLCBidXQgcGxlYXNlIGNvbnNpZGVyIGNvbnRyaWJ1dGluZyB0byB0aGVpciBbd2Vic2l0ZV0oaHR0cHM6Ly9wbHVzLmZhbmdyYXBocy5jb20vc2hvcC8pIGlmIHlvdSBmb3VuZCB0aGlzIHByb2plY3QgaW5mb3JtYXRpdmUuDQoNCjwvcD4NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQprbml0cjo6b3B0c19rbml0JHNldChyb290LmRpciA9ICdDOi9Vc2Vycy9BZG1pbi9Eb2N1bWVudHMvTGVhcm5pbmcgUHl0aG9uIEZvbGRlcjEvUHl0aG9uIEVzc2VuY2UgVHJhaW5pbmcvRmFudGFzeS1CYXNlYmFsbC9EYXRhJykNCm9wdGlvbnMoa25pdHIudGFibGUuZm9ybWF0ID0gImh0bWwiKSANCm9wdGlvbnMoZGlnaXRzPTIpDQpvcHRpb25zKHNjaXBlbiA9IDEwMCkNCmBgYA0KDQojIFByb2plY3QgU2NvcGUgey50YWJzZXQgLnRhYnNldC1waWxsc30NCg0KIyMgT2JqZWN0aXZlDQoNClRoaXMgcHJvamVjdCBpcyBkZXNpZ25lZCB0byBzaG93Y2FzZSBob3cgVXNpbmcgYSBQZXJjZW50aWxlIEJhc2VkIFdvcnRoIFN5c3RlbSB2YWx1ZXMgRmFudGFzeSBCYXNlYmFsbCBQbGF5ZXJzIHRocm91Z2ggYSBJbm5pbmcgUGl0Y2hlZCAoSVApIHdlaWdodGVkIHByb2plY3Rpb24NCg0KVGhlIENhdGVnb3JpZXMgdXNlZCBmb3IgcHJlZGljdGlvbiB2YWx1YXRpb24gYXJlIHllYXItZW5kIHJhbmtpbmdzIGZvciB0aGUgZm9sbG93aW5nIG1ldHJpY3M6DQoNCi0gICBXaW5zDQotICAgU2F2ZXMNCi0gICBTdHJpa2UgT3V0cw0KLSAgIEVSQSAoIDkgXCogRWFybmVkIFJ1bnMgcGVyIElubmluZyBQaXRjaGVkKQ0KLSAgIFdISVAgKFdhbGtzIGFuZCBIaXRzIHBlciBJbm5pbmcgUGl0Y2hlZCkNCi0gICBIb2xkcw0KDQohW10oSW50cm9DaGFydDZ4Ni5wbmcpDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyBQcm9jZXNzaW5nIHRoZSBEYXRhIHsudGFic2V0IC50YWJzZXQtcGlsbHN9DQoNCiMjIEdldHRpbmcgRGF0YSBJbnRvIFINCg0KIyMjIExvYWQgTGlicmFyaWVzDQoNCjxwIHN0eWxlPSJjb2xvcjpibGFjazsiPg0KDQoqRmlyc3Qgd2UgbmVlZCB0byBsb2FkIHRoZSBwYWNrYWdlcyB0aGF0IFIgbmVlZHMgdG8gcnVuIHRoZSBhbmFseXNpcyoNCg0KPC9wPg0KDQpgYGB7ciBsb2FkIGxpYnJhcnksbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHNxbGRmKSAjU1FMIGluIFINCmxpYnJhcnkoc2tpbXIpICNTdW1tYXJpZXMgYW5kIHVzZWZ1bCBmb3IgcmVtb3ZpbmcgbG93ICUgZGF0YQ0KbGlicmFyeShnZ3Bsb3QyKSAjUGxvdHRpbmcgRnVuY3Rpb25zDQpsaWJyYXJ5KHBseXIpICNzbGlnaHRseSBkZXByZWNhdGVkIGRhdGEgY2xlYW5pbmcNCmxpYnJhcnkoZHBseXIpICNzbGlnaHRseSB1cGRhdGVkIGRhdGEgY2xlYW5pbmcNCmxpYnJhcnkodGlkeXZlcnNlKSAjdGlkeXZlcnNlIGRhdGEgY2xlYW5pbmcgdW5pdmVyc2UNCmxpYnJhcnkoY2FyZXQpICN3cmFwcGVyIGZvciBjcmVhdGluZywgdHVuaW5nIGFuZCB2YWxpZGF0aW5nIG1vZGVscw0KbGlicmFyeSh4Z2Jvb3N0KSAjcGFja2FnZSBmb3IgY3JlYXRpbmcgcmVncmVzc2lvbiB0cmVlIG1vZGVsDQpsaWJyYXJ5KHZ0cmVhdCkgIyB1c2VmdWwgcGFja2FnZSBmb3IgdHJlYXRpbmcgZGF0YSBiZWZvcmUgbW9kZWxpbmcgDQpsaWJyYXJ5KE1hdHJpeCkgI2NyZWF0aW5nIG1hdHJpY2llcyBmb3IgeGdib29zdA0KbGlicmFyeShtZ2N2KQ0KbGlicmFyeShtb21lbnRzKSAjZm9yIG1lYXN1cmluZyBza2V3bmVzcw0KbGlicmFyeShkYXRhLnRhYmxlKSAjYWx0ZXJuYXRpdmUgdG8gZHBseXIgd2UgdXNlIHRvIGNyZWF0ZSBsYWdzDQpsaWJyYXJ5KHBkcCkgI3BhcnRpYWwgZGVwZW5kZW5jZSBncmFwaHMNCmxpYnJhcnkodmlwKSAjdmFyaWFibGUgaW1wb3J0YW5jZSANCmxpYnJhcnkoZ3JpZCkgI3B1dCBtdWx0aXBsZSBwbG90cyBvbiBvbmUgZ3JpZA0KbGlicmFyeShncmlkRXh0cmEpICNhZGRpdGlvbmFsIGdyaWQgZnVuY3Rpb25hbGl0eQ0KbGlicmFyeShqYW5pdG9yKSAjb25lIGZ1bmN0aW9uIHVzZWQgdG8gY2xlYW4gdHJhbnNwb3NlZCBkYXRhIHNldA0KbGlicmFyeShnZ3B1YnIpICNmb3IgcXEgcGxvdCANCmxpYnJhcnkob3dtcikgI1JlbW92aW5nIFByZWZpeGVzDQpsaWJyYXJ5KGthYmxlRXh0cmEpICMgZm9ybWF0dGluZyBIVE1MIFRhYmxlcw0KbGlicmFyeShmb3JtYXR0YWJsZSkgIyBmb3JtYXR0aW5nIEhUTUwgVGFibGVzDQoNCmBgYA0KDQpUaGUgXCMgY29tbWVudHMgZ2VuZXJhbGx5IGV4cGxhaW4gd2hhdCBhZGRpdGlvbmFsIGZ1bmN0aW9uYWxpdHkgZWFjaCBsaWJyYXJ5IGFkZHMgdG8gUg0KDQojIyMgTG9hZCBpbiBEYXRhDQoNCkFsbCBkYXRhIGlzIGRvd25sb2FkZWQgZnJvbSBGYW4gR3JhcGhzIGZyb20gdGhpcyBbbG9jYXRpb25dKGh0dHBzOi8vd3d3LmZhbmdyYXBocy5jb20vbGVhZGVycy5hc3B4P3Bvcz1hbGwmc3RhdHM9cGl0JmxnPWFsbCZxdWFsPTAmdHlwZT03JnNlYXNvbj0yMDIxJm1vbnRoPTAmc2Vhc29uMT0yMDE1JmluZD0xJnRlYW09MCZyb3N0PTAmYWdlPTAmZmlsdGVyPSZwbGF5ZXJzPTAmc3RhcnRkYXRlPTIwMTUtMDEtMDEmZW5kZGF0ZT0yMDIxLTEyLTMxKS4gVGhlIGRhdGEgaXMgYWxzbyBhdmFpbGFibGUgb24gbXkgR2l0aHViIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vZGlzc2lwYXRpb24vRmFudGFzeS1CYXNlYmFsbCkuIFRoZXJlIGFyZSBwbGF5ZXIgbGV2ZWwgYW5kIHRlYW0gZGF0YSBzZXRzDQoNCmBgYHtyIGRhdGEgcmVhZC1pbiwgcmVzdWx0cz0gJ2hpZGUnLG1lc3NhZ2U9RkFMU0V9DQoNCiNkYXRhIHJlYWQtaW4NCnBpdGNoZXJfZGF0YSA8LSByZWFkX2NzdigiRmFuR3JhcGhzIExlYWRlcmJvYXJkX1BpdGNoaW5nMjBJUC5jc3YiKQ0KDQojVGVhbSBkYXRhc2V0cw0KRkRHX1RlYW0gPSByZWFkX2NzdigiRmFuR3JhcGhzIExlYWRlcmJvYXJkX1RlYW0uY3N2IikNCg0KDQojQ3JlYXRlIGEgcHJlZml4IGZvciBhbGwgdGVhbSBzdGF0cyB0aGF0IHN0YXJ0cyB3aXRoIFRfDQpGREdfVGVhbTIgPC0gRkRHX1RlYW0gJT4lIA0KICByZW5hbWVfd2l0aCggfiBwYXN0ZTAoIlRfIiwgLngpKQ0KYGBgDQoNCiMjIyBDaGVja2luZyBUZWFtIERhdGENCg0KYHN0cmAgZ2l2ZSBpbmZvcm1hdGlvbiBhYm91dCBhbiBvYmplY3QsIHdoaWxlIGBza2ltYCBwcm92aWRlcyBhIGN1c3RvbWl6YWJsZSBzdW1tYXJ5DQoNCmBgYHtyIGNoZWNraW5nIHRlYW0gZGF0YX0NCg0KI091dHB1dCBub3Qgc2hvd24gZm9yIHNwYWNlDQojc3RyKEZER19UZWFtMikNCg0Kc2tpbShGREdfVGVhbTIpICU+JSAgDQogIHRpYmJsZTo6YXNfdGliYmxlKCkgI1JlbW92ZSB0aGlzIG9wdGlvbiBmb3IgYSBub3JtYWwgSFRNTCB0YWJsZQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyBVbmRlcnN0YW5kaW5nIHRoZSBEYXRhc2V0DQoNCiMjIyBFeHBsb3JpbmcgdGhlIGRhdGFzZXQNCg0KYHNraW1gIGxldCdzIHVzIHNlZSBob3cgdGhlIGRhdGEgd2FzIGltcG9ydGVkIGludG8gUi4gRG9jdW1lbnRhdGlvbiBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9za2ltci92aWduZXR0ZXMvc2tpbXIuaHRtbCkgIA0KDQpgYGB7cn0NCg0KI0Z1bGwgRGF0YXNldCBkaW1lbnNpb25zDQoNCnNraW1yOjpza2ltKHBpdGNoZXJfZGF0YSkgJT4lIA0KICB0aWJibGU6OmFzX3RpYmJsZSgpICU+JSAgI1JlbW92ZSB0aGlzIG9wdGlvbiBmb3IgYSBub3JtYWwgSFRNTCB0YWJsZQ0KICBzZWxlY3Qoc2tpbV90eXBlLHNraW1fdmFyaWFibGUsY29tcGxldGVfcmF0ZSkgJT4lIA0KICBmaWx0ZXIoY29tcGxldGVfcmF0ZSA+MC4zMCkgIzI1MCBWYXJpYWJsZXMNCg0KI3NraW1fdHlwZSAtIGNoYXJhY3RlciBvciBudW1lcmljDQojc2tpbV92YXJpYWJsZSAtIG5hbWUgb2YgdmFyaWFibGUNCiNjb21wbGV0ZV9yYXRlIC0gJSBvZiBkYXRhIHRoYXQgaXMgbm90IG1pc3NpbmcNCiNmaWx0ZXIgLSBvbmx5IGtlZXAgdmFyaWFibGVzIHRoYXQgaGF2ZSAzMCUgb2YgZGF0YSBwb3B1bGF0ZWQNCmBgYA0KKioqICANCg0KDQpBZGRpdGlvbmFsbHkgbGV0J3MgbG9vayBhdCBob3cgdmFyaWFibGVzIHZhcnkgYnkgeWVhciB0byBzZWUgaWYgdGhlcmUgYXJlIGFueSBkaXNjcmVwYW5jaWVzIHRoZXJlDQoNCmBgYHtyfQ0KDQojSXQgbG9va3MgbGlrZSBvbmUgeWVhciwgdGhlcmUgd2VyZSBmZXdlciBnYW1lcyBwbGF5ZWQsIGFuZCB0aGVyZSBpcyBhIGNsZWFyIGRyb3Agb2ZmIGluIGhvbWUgcnVucw0KcGl0Y2hlcl9kYXRhX2Rpc3QgPQ0KcGl0Y2hlcl9kYXRhICU+JSANCiBncm91cF9ieShTZWFzb24pICU+JSANCiAgc3VtbWFyaXplIChNYXhfR2FtZXMgPSBtYXgoRyksDQogICAgICAgICAgICAgQXZnX1c9IG1lYW4oVykNCiAgICAgICAgICAgICApDQoNCnBpdGNoZXJfZGF0YV9kaXN0DQoNCiNQbG90IFdpbiBEYXRhIGJ5IFllYXINCmdncGxvdChwaXRjaGVyX2RhdGFfZGlzdCwgYWVzKFNlYXNvbiwgQXZnX1cpKSArDQogIGdlb21fY29sKCkrDQogIGdndGl0bGUoIkF2ZXJhZ2UgV2lucyBieSBZZWFyIikrDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsc2l6ZSA9IDIyLGNvbG9yID0ic3RlZWwgYmx1ZSIpKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyBDbGVhbmluZyBhbmQgQ3JlYXRpbmcgSW5pdGlhbCBEYXRhc2V0IGZvciBNb2RlbA0KDQpXaGF0IGFyZSBzb21lIGlzc3VlcyB3aXRoIHRoZSBkYXRhPw0KDQoxLiAgTWFueSBvZiBWYXJpYWJsZXMsIHN1Y2ggYXMgSyUsIGFyZSBiZWluZyByZWFkIGluIGFzIGNoYXJhY3RlcnMNCg0KICAgIC0gICBPbmx5IFRlYW0gYW5kIFBsYXllciBOYW1lIHNob3VsZCBiZSBjaGFyYWN0ZXJzDQoNCjIuICBUaGVyZSBpcyBzcG90dHkgZGF0YSBjb3ZlcmFnZSBpbiBzb21lIG9mIHRoZSB2YXJpYWJsZXMgKFx+VmFyaWFibGVzIGhhdmUgbGVzcyB0aGFuIDMwJSBDb3ZlcmFnZSkNCg0KMy4gIDIwMjAgRGF0YSBvbmx5IGluY2x1ZGVzIDYwIGdhbWVzIHdvcnRoIG9mIGRhdGENCg0KICAgIC0gICBUaGlzIHdhcyBhIHNlYXNvbiBzaG9ydGVuZWQgZHVlIHRvIENvdmlkLTE5DQoNCjQuICBUZWFtIERhdGEgbmVlZHMgdG8gYmUgYXBwZW5kZWQgdG8gcGl0Y2hlciBEYXRhIGJ5IFRlYW0gTmFtZQ0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIENsZWFubHkgQ2hhbmdpbmcgYWxsIFZhcmlhYmxlcyB0aGF0IGFyZSBjaGFyYWN0ZXJzIHRvIG51bWVyaWMuICANCg0KVGhlcmUgYXJlIHNldmVyYWwgd2F5cyB0byBkbyB0aGlzLCB3ZSB3aWxsIGlkZW50aWZ5IHRoZSB2YXJpYWJsZXMgd2Ugd2FudCB0byBjaGFuZ2UgdGhhdCBhcmUgbWlzLWlkZW50aWZpZWQuIGBwYXJzZV9udW1iZXJgIGNhbiBiZSB1c2VkIHRvIHB1bGwgbnVtYmVycyBmcm9tIHRoZXNlIHZhcmlhYmxlcy4gQWRkaXRpb25hbCB3YXlzIHRvIHRhY2tsZSB0aGlzIGNhbiBiZSBmb3VuZCBbaGVyZS5dKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzgzMjkwNTkvaG93LXRvLWNvbnZlcnQtY2hhcmFjdGVyLW9mLXBlcmNlbnRhZ2UtaW50by1udW1lcmljLWluLXIpDQoNCmBgYHtyfQ0KDQojU2VsZWN0IENvbHVtbiBuYW1lcyB0aGF0IGFyZSBjaGFyYWN0ZXJzIGJ1dCBub3QgVGVhbSBvciBOYW1lLCBUaGVzZSBzaG91bGQgYmUgcGVyY2VudGFnZXMNCnBpdGNoZXJfZGF0YV9jaGFyc190b19jb252ZXJ0IDwtIHBpdGNoZXJfZGF0YSAlPiUgDQogIHNlbGVjdF9pZihpcy5jaGFyYWN0ZXIpJT4lIHNlbGVjdCgtVGVhbSwtTmFtZSkgJT4lIA0KICBtdXRhdGVfYWxsIChmdW5jdGlvbih4KSBhcy5udW1lcmljKHJlYWRyOjpwYXJzZV9udW1iZXIoeCkpLzEwMCkNCiNOb3RlIDogVGhlcmUgYXJlIGFkZGl0aW9uYWwgd2F5cyB0byBkbyB0aGlzLCB0aGlzIGlzIGp1c3Qgb25lIHNvbHV0aW9uDQoNCg0KI1dlIGNhbiBleGNsdWRlIHRoZSB2YXJpYWJsZXMgd2UgY29udmVydGVkIGFuZCByZWludHJvZHVjZSB0aGVtDQpwaXRjaGVyX2RhdGFfbnVtIDwtIHBpdGNoZXJfZGF0YSAlPiUgc2VsZWN0KC1jb2xuYW1lcyhwaXRjaGVyX2RhdGFfY2hhcnNfdG9fY29udmVydCkpDQoNCnBpdGNoZXJfZGF0YTIgPSBjYmluZChwaXRjaGVyX2RhdGFfbnVtLHBpdGNoZXJfZGF0YV9jaGFyc190b19jb252ZXJ0KSAlPiUgDQogIHNlbGVjdCAoY29sbmFtZXMocGl0Y2hlcl9kYXRhKSkgJT4lICAjcHJlc2VydmUgb3JpZ2luYWwgb3JkZXIgDQogIGRwbHlyOjpyZW5hbWUoZmx5YmFsbF9wZXJjID0gYEZCJS4uLjUwYCxmYXN0YmFsbF9wZXJjID0gYEZCJS4uLjc0YCkgI3JlbmFtZSB0d28gYW1iaWd1b3VzIGNvbHVtbnMNCiAgDQpza2ltKHBpdGNoZXJfZGF0YTIpICU+JSANCiAgYXNfdGliYmxlKCkgJT4lIA0KICBncm91cF9ieShza2ltX3R5cGUpICU+JSANCiAgY291bnQoKQ0KDQoNCiNMb2dpY2FsIHZhcmlhYmxlcyBhcmUgUidzIGJlc3QgZ3Vlc3MsIGluIG91ciBjYXNlIHRoZXkgYXJlIGFsbCBOQSdzIGFuZCB3aWxsIGJlIHJlbW92ZWQgYXQgYSBsYXRlciBzdGVwDQoNCmBgYA0KDQpUaGUgc2FtZSBjYW4gYmUgZG9uZSBmb3IgdGhlIFRlYW0gRGF0YSB0aGF0IGlzIGxvYWRlZA0KDQpgYGB7cn0NCg0KI1NlbGVjdCBDb2x1bW4gbmFtZXMgdGhhdCBhcmUgY2hhcmFjdGVycyBidXQgbm90IFRlYW0gb3IgTmFtZSwgVGhlc2Ugc2hvdWxkIGJlIHBlcmNlbnRhZ2VzDQpGREdfVGVhbTJfY2hhcnNfdG9fY29udmVydCA8LSBGREdfVGVhbTIgJT4lIA0KICBzZWxlY3RfaWYoaXMuY2hhcmFjdGVyKSU+JSBzZWxlY3QoLVRfVGVhbSkgJT4lIA0KICBtdXRhdGVfYWxsIChmdW5jdGlvbih4KSBhcy5udW1lcmljKHJlYWRyOjpwYXJzZV9udW1iZXIoeCkpLzEwMCkNCiNLZWVwIGluIG1pbmQsIHBhcnNlIG51bWJlciBtYXkgbWFrZSBhY3R1YWwgY2hhcmFjdGVycyBpbnRvIG51bWVyaWNhbCB2YXJpYWJsZXMgc28gY2FyZWZ1bGx5IGNoZWNrIHlvdXIgZGF0YSBiZWZvcmUgdXNpbmcNCg0KI1dlIGNhbiBleGNsdWRlIHRoZSB2YXJpYWJsZXMgd2UgY29udmVydGVkIGFuZCByZWludHJvZHVjZSB0aGVtDQpGREdfVGVhbTJfbnVtIDwtIEZER19UZWFtMiAlPiUgc2VsZWN0KC1jb2xuYW1lcyhGREdfVGVhbTJfY2hhcnNfdG9fY29udmVydCkpDQoNCkZER19UZWFtMyA9IGNiaW5kKEZER19UZWFtMl9udW0sRkRHX1RlYW0yX2NoYXJzX3RvX2NvbnZlcnQpICU+JSANCiAgc2VsZWN0IChjb2xuYW1lcyhGREdfVGVhbTIpKSAlPiUgICNwcmVzZXJ2ZSBvcmlnaW5hbCBvcmRlcg0KZHBseXI6OnJlbmFtZShUX2ZseWJhbGxfcGVyYyA9IGBUX0ZCJS4uLjQ1YCxUX2Zhc3RiYWxsX3BlcmMgPSBgVF9GQiUuLi43MmApICAjcmVuYW1lIHR3byBhbWJpZ3VvdXMgY29sdW1ucw0KDQpza2ltKEZER19UZWFtMykgJT4lIA0KICBhc190aWJibGUoKSAlPiUgDQogIGdyb3VwX2J5KHNraW1fdHlwZSkgJT4lIA0KICBjb3VudCgpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyBGaWx0ZXJpbmcgRGF0YSB3aXRoIExvdyBDb3ZlcmFnZQ0KDQpJIGNob29zZSAzMCUgY292ZXJhZ2Ugb2YgZGF0YSBuZWNlc3NhcnkgYnV0IHRoaXMgY2FuIGJlIGFkanVzdGVkIHVwIG9yIGRvd24uIFRoaXMgd2lsbCBhbHNvIGdldCByaWQgb2YgY29sdW1ucyB0aGF0IGFyZSBhbGwgYE5BYC4NCg0KYGBge3J9DQoNCiMgS2VlcCB2YXJpYWJsZXMgd2l0aCBlbm91Z2ggdmFsdWVzIChOZWVkIDMwJSBkYXRhIGNvdmVyYWdlIHJhdGUgaGVyZSkNClBsYXllcl9jb2xzX3RvX2tlZXAgPQ0Kc2tpbShwaXRjaGVyX2RhdGEyKSAlPiUgDQogIGRwbHlyOjpzZWxlY3Qoc2tpbV90eXBlLCBza2ltX3ZhcmlhYmxlLCBjb21wbGV0ZV9yYXRlKSAlPiUgDQogIGZpbHRlciAoY29tcGxldGVfcmF0ZSA+IDAuMzApDQoNCiNUcmFuc3Bvc2UgUm93cyB0byBnZXQgY29sdW1uIG5hbWVzIGFzIHNraW0gbWVsdHMgdGhlIGRhdGENClBsYXllcl9jb2xzX3RvX2tlZXBfdHJhbnNwb3NlID0gdChQbGF5ZXJfY29sc190b19rZWVwKSANCg0KI2V4dHJhY3QgdGhlIGNvbG5hbWVzIHdlIHdvdWxkIGxpa2UgdG8ga2VlcA0KUGxheWVyX2NvbHNfdG9fa2VlcCA9IGNvbG5hbWVzKGphbml0b3I6OnJvd190b19uYW1lcyhQbGF5ZXJfY29sc190b19rZWVwX3RyYW5zcG9zZSxyb3dfbnVtYmVyID0gMikpDQoNCiNPbmx5IGtlZXAgdGhlIGNvbHVtbnMgZGVzaWduYXRlZCB0byBoYXZlIG92ZXIgMzAlIG9mIHRoZWlyIGRhdGEgcG9wdWxhdGVkIG9yIGdyZWF0ZXINCnBpdGNoZXJfZGF0YTMgPSBwaXRjaGVyX2RhdGEyICU+JSANCiAgc2VsZWN0KG9uZV9vZihQbGF5ZXJfY29sc190b19rZWVwKSkgDQoNCmBgYA0KDQoqUmVwZWF0IHRoZSBwcm9jZXNzIGZvciBUZWFtIFZhcmlhYmxlcyoNCg0KYGBge3J9DQpUZWFtX2NvbHNfdG9fa2VlcCA9DQpza2ltKEZER19UZWFtMykgJT4lIA0KICBkcGx5cjo6c2VsZWN0KHNraW1fdHlwZSwgc2tpbV92YXJpYWJsZSwgY29tcGxldGVfcmF0ZSkgJT4lIA0KICBmaWx0ZXIgKGNvbXBsZXRlX3JhdGUgPiAwLjMwKQ0KDQoNCiNUcmFuc3Bvc2UgUm93cyB0byBnZXQgY29sdW1uIG5hbWVzIGFzIHNraW0gbWVsdHMgdGhlIGRhdGENClRlYW1fY29sc190b19rZWVwX3RyYW5zcG9zZSA9IHQoVGVhbV9jb2xzX3RvX2tlZXApIA0KDQojZXh0cmFjdCB0aGUgY29sbmFtZXMgd2Ugd291bGQgbGlrZSB0byBrZWVwDQpUZWFtX2NvbHNfdG9fa2VlcCA9IGNvbG5hbWVzKGphbml0b3I6OnJvd190b19uYW1lcyhUZWFtX2NvbHNfdG9fa2VlcF90cmFuc3Bvc2Uscm93X251bWJlciA9IDIpKQ0KDQojT25seSBrZWVwIHRoZSBjb2x1bW5zIGRlc2lnbmF0ZWQgdG8gaGF2ZSBvdmVyIDMwJSBvZiB0aGVpciBkYXRhIHBvcHVsYXRlZCBvciBncmVhdGVyDQpGREdfVGVhbTQgPSBGREdfVGVhbTMgJT4lIA0KICBzZWxlY3Qob25lX29mKFRlYW1fY29sc190b19rZWVwKSkgDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyBDcmVhdGluZyBWYXJpYWJsZXMgTm9ybWFsaXplZCBieSBZZWFyDQoNClNvbWUgVmFyaWFibGVzIHdpbGwgbmVlZCB0byBiZSBub3JtYWxpemVkIGJ5IElubmluZ3NfUGl0Y2hlZCAoSVApIGlmIHRoZXkgYXJlbid0IGEgcGVyY2VudGFnZSBhbHJlYWR5LiBSZW1haW5pbmcgVmFyaWFibGVzIGFyZSBwZXJjZW50YWdlcyBvciBpbmRpY2VzIHNvIHdpbGwgbm90IG5lZWQgdG8gYmUgdHJhbnNmb3JtZWQuIFRoZSBmdWxsIGRhdGEgZGljdGlvbmFyeSBmb3IgdGhlc2UgdmFyaWFibGVzIGNhbiBiZSBmb3VuZCBvbiBGYW5HcmFwaCdzIHdlYnNpdGUgW2hlcmUuXShodHRwczovL2xpYnJhcnkuZmFuZ3JhcGhzLmNvbS9waXRjaGluZy9jb21wbGV0ZS1saXN0LXBpdGNoaW5nLykgZm9yIHBpdGNoaW5nIHZhcmlhYmxlcyBhbmQgW2hlcmUuXShodHRwczovL2xpYnJhcnkuZmFuZ3JhcGhzLmNvbS9vZmZlbnNlL29mZmVuc2l2ZS1zdGF0aXN0aWNzLWxpc3QvKSBmb3IgaGl0dGluZyB2YXJpYWJsZXMuDQoNCmBgYHtyfQ0KDQoNCnBpdGNoZXJfZGF0YTQgPSBwaXRjaGVyX2RhdGEzICU+JSANCiAgbXV0YXRlKCAjY3JlYXRlIG5ldyB2YXJpYWJsZXMgYmFzZWQgb24gZXhpc3RpbmcgdmFyaWFibGVzDQogICAgV19JUCA9IFcvSVAsDQogICAgTF9JUCA9ICBML0lQLCANCiAgICBTaE9fSVAgPSBTaE8vSVAsDQogICAgU1ZfSVAgPSBTVi9JUCwNCiAgICBCU19JUCA9IEJTL0lQLA0KICAgIFRCRl9JUCA9IFRCRi9JUCwNCiAgICBIX0lQID0gSC9JUCwNCiAgICBSX0lQID0gUi9JUCwNCiAgICBFUl9JUCA9IEVSL0lQLA0KICAgIEhSX0lQPUhSL0lQLA0KICAgIEJCX0lQPUJCL0lQLA0KICAgIElCQl9JUD1JQkIvSVAsDQogICAgSEJQX0lQPUhCUC9JUCwNCiAgICBXUF9JUD0gV1AvSVAsDQogICAgQktfSVA9QksvSVAsDQogICAgU09fSVA9U08vSVAsDQogICAgR0JfSVAgPSBHQi9JUCwgICAjR3JvdW5kYmFsbHMNCiAgICBGQl9JUCA9ICBGQi9JUCwgICNGbHlCYWxscw0KICAgIExEX0lQID0gTEQvSVAsICAgI0xpbmVEcml2ZXMNCiAgICBJRkZCX0lQID0gSUZGQi9JUCwgICNJbmZpZWxkIEZseSBiYWxscw0KICAgIEJhbGxzX0lQPSBCYWxscy9JUCwNCiAgICBTdHJpa2VzX0lQPSBTdHJpa2VzL0lQLA0KICAgIFBpdGNoZXNfSVA9IFBpdGNoZXMvSVAsDQogICAgUlNfSVA9IFJTL0lQLA0KICAgIElGSF9JUD0gSUZIL0lQLA0KICAgIEJVX0lQPSBCVS9JUCwNCiAgICBCVUhfSVA9IEJVSC9JUCwNCiAgICBQdWxsc19JUD0gUHVsbHMvSVAsDQogICAgSExEX0lQPSBITEQvSVAsICAgDQogICAgU0RfSVA9IFNEL0lQLCAgICANCiAgICBNRF9JUD0gTUQvSVAsICAgIA0KICAgIEJhcnJlbHNfSVA9IEJhcnJlbHMvSVAsDQogICAgSGFyZEhpdHNfSVA9IEhhcmRIaXQvSVANCiAgKSAlPiUgc2VsZWN0KC1MLC1HLC1JUCwtU2hPLC1CUywtKFRCRjpCSyksLShHQjpCVUgpLC1QdWxscywtKFNEOk1EKSwtQmFycmVscywtSGFyZEhpdCwtRXZlbnRzKQ0KICAgICAgICAgICAgICAgDQojd2lsbCBiZSByZW1vdmVkIGFmdGVyIGRhdGEgaXMgbGFnZ2VkIC1GSVAsLShSQVI6V1BBKSwsLSh3RkI6d0NIKSwtKGBFUkEtYDpgeEZJUC1gKSwtU0lFUkEsLShgUkE5LVdBUmA6YEFnZSBSbmdgKSwta3dFUkEsLWB3Q0ggKHBpKWA6YHdTTCAocGkpYCxgSy85K2A6YEhSL0ZCJStgKSANCg0KI3NraW0ocGl0Y2hlcl9kYXRhNCkgJT4lIGFzX3RpYmJsZSgpDQoNCg0KYGBgDQoNCipSZXBlYXQgdGhlIHByb2Nlc3MgZm9yIFRlYW0gVmFyaWFibGVzKg0KDQpgYGB7cn0NCg0KRkRHX1RlYW01ID0gRkRHX1RlYW00ICU+JSANCiAgbXV0YXRlKCAjY3JlYXRlIG5ldyB2YXJpYWJsZXMgYmFzZWQgb24gZXhpc3RpbmcgdmFyaWFibGVzDQogICAgVF9IX1RfUEEgPSBUX0gvVF9QQSwNCiAgICBUX3gxQl9UX1BBID0gVF8xQi9UX1BBLCAjbm90ZTogUiBjYW4ndCBoYXZlIHZhcmlhYmxlcyBzdGFydCB3aXRoIGEgbnVtYmVyDQogICAgVF94MmJfVF9QQSA9IFRfMkIvVF9QQSwNCiAgICBUX3gzYl9UX1BBID0gVF8zQi9UX1BBLA0KICAgIFRfSFJfVF9QQSA9IFRfSFIvVF9QQSwNCiAgICBUX1JfVF9QQSA9IFRfUi9UX1BBLA0KICAgIFRfUkJJX1RfUEEgPSBUX1JCSS9UX1BBLA0KICAgIFRfQkJfVF9QQSA9IFRfQkIvVF9QQSwNCiAgICBUX0lCQl9UX1BBID0gVF9JQkIvVF9QQSwNCiAgICBUX1NPX1RfUEE9VF9TTy9UX1BBLA0KICAgIFRfSEJQX1RfUEE9VF9IQlAvVF9QQSwNCiAgICBUX1NGX1RfUEE9VF9TRi9UX1BBLA0KICAgIFRfU0hfVF9QQT1UX1NIL1RfUEEsDQogICAgVF9HRFBfVF9QQT0gVF9HRFAvVF9QQSwjZ3JvdW5kIGludG8gZG91YmxlIHBsYXkNCiAgICBUX1NCX1RfUEE9VF9TQi9UX1BBLA0KICAgIFRfQ1NfVF9QQT1UX0NTL1RfUEEsDQogICAgVF9HQl9UX1BBID0gVF9HQi9UX1BBLCAgICNHcm91bmRiYWxscw0KICAgIFRfRkJfVF9QQSA9ICBUX0ZCL1RfUEEsICAjRmx5QmFsbHMNCiAgICBUX0xEX1RfUEEgPSBUX0xEL1RfUEEsICAgI0xpbmVEcml2ZXMNCiAgICBUX0lGRkJfVF9QQSA9IFRfSUZGQi9UX1BBLCAgI0luZmllbGQgRmx5IGJhbGxzDQogICAgVF9QaXRjaGVzX1RfUEE9IFRfUGl0Y2hlcy9UX1BBLA0KICAgIFRfQmFsbHNfVF9QQT0gVF9CYWxscy9UX1BBLA0KICAgIFRfU3RyaWtlc19UX1BBPSBUX1N0cmlrZXMvVF9QQSwNCiAgICBUX0lGSF9UX1BBPSBUX0lGSC9UX1BBLA0KICAgIFRfQlVfVF9QQT0gVF9CVS9UX1BBLA0KICAgIFRfQlVIX1RfUEE9IFRfQlVIL1RfUEEsDQogICAgVF9QSF9UX1BBPSBUX1BIL1RfUEEsDQogICAgVF9CYXJyZWxzX1RfUEE9IFRfQmFycmVscy9UX1BBLA0KICAgIFRfSGFyZEhpdHNfVF9QQT0gVF9IYXJkSGl0L1RfUEENCiAgKSAlPiUgc2VsZWN0KC0oVF9IOlRfQ1MpLC0oVF9HQjpUX0JVSCksLVRfUEgsLVRfQmFycmVscywtVF9IYXJkSGl0LC1UX0V2ZW50cykgI0Ryb3AgdGhlIG9sZCB2YXJpYWJsZXMNCg0KDQojc2tpbShGREdfVGVhbTUpICU+JSBhc190aWJibGUoKQ0KDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyBDcmVhdGluZyBMYWdnZWQgVmFyaWFibGVzDQoNClRoZXJlIGFyZSBzZXZlcmFsIHdheXMgdG8gbGFnIGEgZGF0YXNldCAqKkJZIEdST1VQKiouXA0KXCogYERwbHlyYCB3YXkgaXMgW2hlcmUuXShodHRwczovL3N0YXRpc3RpY3NnbG9iZS5jb20vY3JlYXRlLWxhZ2dlZC12YXJpYWJsZS1ieS1ncm91cC1pbi1yKS5cDQpcKiBUaGUgYGRhdGEudGFibGVgICh0aGUgbWV0aG9kIHVzZWQgYmVsb3cpIGlzIFtoZXJlLl0oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMjYyOTE5ODgvaG93LXRvLWNyZWF0ZS1hLWxhZy12YXJpYWJsZS13aXRoaW4tZWFjaC1ncm91cCkNCg0KYGBge3J9DQojTm90ZSB3ZSB3aWxsIG9ubHkgYmUgbGFnZ2luZyB0aGUgcGxheWVyIGxldmVsIGRhdGEsIGFzIHRoZSBwcmV2aW91cyB5ZWFyJ3MgdGVhbSBwZXJmb3JtYW5jZSBzaG91bGRuJ3QgaW1wYWN0IGN1cnJlbnQgcGVyZm9ybWFuY2UNCg0KDQojT3JkZXIgdGhlIGRhdGFzZXQgYnkgbGFnIGNvbHVtbnMNCnBpdGNoZXJfZGF0YTUgPSAgYXJyYW5nZShwaXRjaGVyX2RhdGE0LCBwbGF5ZXJpZCxTZWFzb24pICNwbGF5ZXJpZCBpcyB0aGUgRmFuZ3JhcGggaWQgYXNzaWduZWQgdG8gZWFjaCBwbGF5ZXINCg0KIyBDb252ZXJ0IGRhdGFmcmFtZSB0byBkYXRhLnRhYmxlIGZvcm1hdA0KRFRfcGl0Y2hlciA9IGRhdGEudGFibGUocGl0Y2hlcl9kYXRhNSkNCg0KI2Rlc2lnbmF0ZSBjb2x1bW5zIHRvIGxhZyAtIHdoaWNoIGlzIGFsbCBvZiB0aGVtDQpjb2xzMSA9IGNvbG5hbWVzKHBpdGNoZXJfZGF0YTUpDQphbnNjb2xzID0gcGFzdGUoImxhZyIsIGNvbHMxLCBzZXA9Il8iKQ0KRFRfcGl0Y2hlclssIChhbnNjb2xzKSA6PSBkYXRhLnRhYmxlOjpzaGlmdCguU0QsIDEsIE5BLCAibGFnIiksYnkgPSdwbGF5ZXJpZCcsIC5TRGNvbHM9Y29sczFdICNDcmVhdGUgMSBwZXJpb2QgbGFncyBieSB5ZWFyDQoNCnBpdGNoZXJfZGF0YTYgPSBhcy5kYXRhLmZyYW1lKERUX3BpdGNoZXIpICU+JSBzZWxlY3QoLWxhZ19wbGF5ZXJpZCwgLWxhZ19UZWFtLCAtbGFnX1NlYXNvbiwgLWxhZ19BZ2UsLWxhZ19OYW1lKQ0KDQpuY29sKHBpdGNoZXJfZGF0YTUpICMyNTEgLSBubyBsYWdzDQpuY29sKHBpdGNoZXJfZGF0YTYpICM0OTcgLSBsYWdnZWQgZGF0YSB+ICgyNTEgKiAyKS01DQoNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIE1lcmdpbmcgVGVhbSBhbmQgUGxheWVyIERhdGENCg0KV2UgY2FuIHVzZSBlaXRoZXIgdGhlIGBtZXJnZWAgZnVuY3Rpb24gb3IgdGhlIFNRTCBmdW5jdGlvbmFsaXR5IHByb3ZpZGVkIGJ5IHRoZSBgc3FsZGZgIHBhY2thZ2UgdG8gam9pbiB0aGUgbGFnZ2VkIHBsYXllciBsZXZlbCBkYXRhIHRvIHRoZSBUZWFtIGxldmVsIGRhdGENCg0KYGBge3J9DQoNCmRmX3BpdGNoaW5nX2luaXQgPSBzcWxkZigNCiAgIg0KICBzZWxlY3QgYS4qLCBiLioNCiAgZnJvbSBwaXRjaGVyX2RhdGE2IGENCiAgbGVmdCBqb2luIEZER19UZWFtNSBiDQogIG9uIGEuVGVhbSA9IGIuVF9UZWFtIGFuZCBhLlNlYXNvbiA9IGIuVF9TZWFzb24NCiAgDQogICINCikgICU+JSBzZWxlY3QoLVRfVGVhbSwtVF9TZWFzb24sLVRfQWdlLC1UX0csLVRfQUIpIyBVbm5jZXNzYXJ5IFRlYW0gVmFyaWFibGVzDQoNCg0KbnJvdyhkZl9waXRjaGluZ19pbml0KSAtIG5yb3cocGl0Y2hlcl9kYXRhNikgI2NoZWNrIGlmIGFueSByb3dzIGFyZSBkdXBsaWNhdGVkDQoNCg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIENyZWF0aW5nIFJhbmtpbmdzIGZvciBQbGF5ZXJzIEJhc2VkIE9uIFBlcmNlbnRpbGVzIHsudGFic2V0IC50YWJzZXQtcGlsbHN9DQoNCldlIGNhbiB1c2UgUGVyY2VudGlsZSBiYXNlZCByYW5raW5nIHRvIGdldCByYW5raW5ncyBmb3IgcGxheWVycyBmcm9tIHRoZSAyMDIxIHNlYXNvbi4NCg0KIyMgV29ydGggb2YgZWFjaCBzdGF0DQoNCiMjIyBDYWxjdWxhdGluZyBwYXN0IHBlcmZvcm1hbmNlDQoNCkVhY2ggcGxheWVyIGdvZXMgZnJvbSBhIDAlIHRvIDEwMCUgb24gZWFjaCBwZXJjZW50aWxlIHN0YXQgdGhhdCBpcyB1c2VkIGZvciBjcmVhdGluZyBhIHNjb3Jpbmcgb3Bwb3J0dW5pdHkuIERhdGEgaXMgbm90IG5vcm1hbGl6ZWQgYnkgSVAgYXMgY2VydGFpbiBzdGF0cyBzdWNoIGFzIFdpbnMgd2lsbCBiZSB3b3J0aCBtb3JlIHdoZW4gd2UgZG8uXA0KDQpgYGB7cn0NCg0KI0NhdGVnb3JpZXMgSSBpbmNsdWRlIGFyZToNCiNXaW5zLCBTYXZlcywgV0hJUCwgRVJBLCBTT3MsIEhvbGRzDQoNCmRmX3BpdGNoaW5nX2luaXQyID0gIGRmX3BpdGNoaW5nX2luaXQgJT4lDQojICBhcnJhbmdlKHBsYXllcl9pZCx5ZWFyKSAlPiUgDQogIGdyb3VwX2J5KFNlYXNvbikgJT4lIA0KICBtdXRhdGUoDQogICAgV2luc19zaGFyZSA9IG9yZGVyKG9yZGVyKHJhbmsoV19JUCx0aWVzLm1ldGhvZCA9ICdhdmVyYWdlJyksZGVjcmVhc2luZyA9IEZBTFNFKSkvbigpLA0KICAgICBTT19zaGFyZSA9IG9yZGVyKG9yZGVyKHJhbmsoU09fSVAsdGllcy5tZXRob2QgPSAnYXZlcmFnZScpLGRlY3JlYXNpbmcgPSBGQUxTRSkpL24oKSwNCiAgICAgU1Zfc2hhcmUgPSBvcmRlcihvcmRlcihyYW5rKFNWX0lQLHRpZXMubWV0aG9kID0gJ2F2ZXJhZ2UnKSxkZWNyZWFzaW5nID0gRkFMU0UpKS9uKCksDQogICAgIFdISVBfc2hhcmUgPSBvcmRlcihvcmRlcihyYW5rKFdISVAsdGllcy5tZXRob2QgPSAnYXZlcmFnZScpLGRlY3JlYXNpbmcgPSBGQUxTRSkpL24oKSwNCiAgICAgRVJBX3NoYXJlID0gb3JkZXIob3JkZXIocmFuayhFUkEsdGllcy5tZXRob2QgPSAnYXZlcmFnZScpLGRlY3JlYXNpbmcgPSBGQUxTRSkpL24oKSwNCiAgICBITERfc2hhcmUgPSBvcmRlcihvcmRlcihyYW5rKEhMRF9JUCx0aWVzLm1ldGhvZCA9ICdhdmVyYWdlJyksZGVjcmVhc2luZyA9IEZBTFNFKSkvbigpLA0KICAgIFdvcnRoID0gV2luc19zaGFyZStTT19zaGFyZStTVl9zaGFyZStXSElQX3NoYXJlK0VSQV9zaGFyZStITERfc2hhcmUNCiAgICApICU+JSANCiAgdW5ncm91cCgpIA0KYGBgDQoNCkNoYXJ0IG9mIHRoZSBEaXN0cmlidXRpb24gb2YgaW5pdGlhbCBwZXJjZW50aWxlc1wNCkFzIHRoZSBjaGFydCBiZWxvdyBzaG93cywgdGhlIGRhdGEgaXMgcm91Z2hseSBub3JtYWwuDQoNCmBgYHtyfQ0KDQpza2V3bmVzcygoZGZfcGl0Y2hpbmdfaW5pdDIkV29ydGgpKQ0KDQpnZ3Bsb3QyOjpxcGxvdChkZl9waXRjaGluZ19pbml0MiRXb3J0aCwgbWFpbj0iVG90YWwgUGl0Y2hpbmcgV29ydGggRGF0YXNldCIpICsgZ2VvbV9oaXN0b2dyYW0oY29sb3VyPSJibGFjayIsIGZpbGw9InN0ZWVsYmx1ZSIpDQoNCm1pbihkZl9waXRjaGluZ19pbml0MiRXb3J0aCkNCg0KbWF4KGRmX3BpdGNoaW5nX2luaXQyJFdvcnRoKQ0KDQpnZ3B1YnI6OmdncXFwbG90KGRmX3BpdGNoaW5nX2luaXQyJFdvcnRoKQ0KDQpzaGFwaXJvLnRlc3QoZGZfcGl0Y2hpbmdfaW5pdDIkV29ydGgpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIDIwMjEgUGxheWVyIFJhbmtpbmdzIC0gUGVyIElQIHBlcmZvcm1hbmNlDQoNCiMjIyAyMDIxIFBsYXllciBSYW5raW5ncyAtIFRvcCBXb3J0aCBQbGF5ZXJzIHdpdGggSG9sZHMNCg0KVG90YWwgUmFua2luZ3MgZm9yIHRoZSBwbGF5ZXJzIChVc2luZyA2eDYgU2NvcmluZykgY2FuIGJlIGZvdW5kIFtoZXJlLl0oKSBXaGlsZSBpdCBsb29rcyBsaWtlIG1hbnkgb2YgdGhlIHRvcCBwbGF5ZXJzIGhhdmUgbG93IHdvcnRoIHNjb3JlcywgaXQgaXMgYmVjYXVzZSB3ZSBoYXZlbid0IGFwcGxpZWQgYSBtb2RpZmllciBmb3IgSVAgeWV0LiBXaW5zIGFyZSBoYXJkZXIgdG8gY29tZSBieSByZWxhdGl2ZSB0byBhbnkgb3RoZXIgc3RhdCBhbmQgcmVxdWlyZSBtb3JlIGlubmluZ3MgcGl0Y2hlZC4NCg0KYGBge3Isd2FybmluZz1GQUxTRX0NCg0KDQpkZl9waXRjaGluZ19pbml0Ml9yYXcgPSAgZGZfcGl0Y2hpbmdfaW5pdCAlPiUNCiMgIGFycmFuZ2UocGxheWVyX2lkLHllYXIpICU+JSANCiAgZ3JvdXBfYnkoU2Vhc29uKSAlPiUgDQogIG11dGF0ZSgNCiAgICBXaW5zX3NoYXJlX3JhdyA9IG9yZGVyKG9yZGVyKHJhbmsoVyx0aWVzLm1ldGhvZCA9ICdhdmVyYWdlJyksZGVjcmVhc2luZyA9IEZBTFNFKSkvbigpLA0KICAgICBTT19zaGFyZV9yYXcgPSBvcmRlcihvcmRlcihyYW5rKFNPLHRpZXMubWV0aG9kID0gJ2F2ZXJhZ2UnKSxkZWNyZWFzaW5nID0gRkFMU0UpKS9uKCksDQogICAgIFNWX3NoYXJlX3JhdyA9IG9yZGVyKG9yZGVyKHJhbmsoU1YsdGllcy5tZXRob2QgPSAnYXZlcmFnZScpLGRlY3JlYXNpbmcgPSBGQUxTRSkpL24oKSwNCiAgICAgV0hJUF9zaGFyZSA9IG9yZGVyKG9yZGVyKHJhbmsoV0hJUCx0aWVzLm1ldGhvZCA9ICdhdmVyYWdlJyksZGVjcmVhc2luZyA9IEZBTFNFKSkvbigpLA0KICAgICBFUkFfc2hhcmUgPSBvcmRlcihvcmRlcihyYW5rKEVSQSx0aWVzLm1ldGhvZCA9ICdhdmVyYWdlJyksZGVjcmVhc2luZyA9IEZBTFNFKSkvbigpLA0KICAgIEhMRF9zaGFyZV9yYXcgPSAwLA0KICAgIFdvcnRoID0gV2luc19zaGFyZV9yYXcrU09fc2hhcmVfcmF3K1NWX3NoYXJlX3JhdytXSElQX3NoYXJlK0VSQV9zaGFyZStITERfc2hhcmVfcmF3DQogICAgKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQpzZWxlY3QoLVcsLVNPLC1TViwtV0hJUCwtRVJBLC1ITEQpDQoNCg0KDQpvcHRpb25zKGRpZ2l0cz0yKQ0KDQpkZl9waXRjaGluZ19pbml0MjAyMV9yYXcgPQ0KZGZfcGl0Y2hpbmdfaW5pdDJfcmF3ICU+JSANCiAgZ3JvdXBfYnkoTmFtZSkgJT4lIA0KICBmaWx0ZXIoU2Vhc29uID09IDIwMjEpICU+JSANCiAgYXJyYW5nZShkZXNjKFdvcnRoKSkgJT4lIA0KICBzZWxlY3QoTmFtZSxXaW5zX3NoYXJlX3JhdyxTT19zaGFyZV9yYXcsU1Zfc2hhcmVfcmF3LFdISVBfc2hhcmUsRVJBX3NoYXJlLFdvcnRoKQ0KDQoNCmRmX3BpdGNoaW5nX2luaXQyMDIxX3JhdyAlPiUNCiAgZmlsdGVyIChXb3J0aD4zLjUpICU+JSANCiAga2JsKCkgJT4lIA0KIGthYmxlX21hdGVyaWFsKGMoInN0cmlwZWQiLCAiaG92ZXIiLCJjb25kZW5zZWQiLCJyZXNwb25zaXZlIiksZnVsbF93aWR0aCA9IEYsZml4ZWRfdGhlYWQgPSBUKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAyMDIxIFBsYXllciBSYW5raW5ncyAtIEFjdHVhbCBQZXJmb3JtYW5jZQ0KDQojIyMgMjAyMSBQbGF5ZXIgUmFua2luZ3MgLSBUb3AgV29ydGggUGxheWVycyB3aXRoIEhvbGRzDQoNCldoaWxlIGl0IGxvb2tzIGxpa2UgbWFueSBvZiB0aGUgdG9wIHBsYXllcnMgaGF2ZSBsb3cgd29ydGggc2NvcmVzLCBpdCBpcyBiZWNhdXNlIHdlIGhhdmVuJ3QgYXBwbGllZCBhIG1vZGlmaWVyIGZvciBJUCB5ZXQuDQoNCmBgYHtyLHdhcm5pbmc9RkFMU0V9DQoNCm9wdGlvbnMoZGlnaXRzPTIpDQoNCmRmX3BpdGNoaW5nX2luaXQyMDIxID0NCmRmX3BpdGNoaW5nX2luaXQyICU+JSANCiAgZ3JvdXBfYnkoTmFtZSkgJT4lIA0KICBmaWx0ZXIoU2Vhc29uID09IDIwMjEpICU+JSANCiAgYXJyYW5nZShkZXNjKFdvcnRoKSkgJT4lIA0KICBzZWxlY3QoTmFtZSxXaW5zX3NoYXJlLFNPX3NoYXJlLFNWX3NoYXJlLFdISVBfc2hhcmUsRVJBX3NoYXJlLEhMRF9zaGFyZSxXb3J0aCkNCg0KDQpkZl9waXRjaGluZ19pbml0MjAyMSAlPiUNCiAgZmlsdGVyIChXb3J0aD4yLjkpICU+JSANCiAga2JsKCkgJT4lIA0KIGthYmxlX21hdGVyaWFsKGMoInN0cmlwZWQiLCAiaG92ZXIiLCJjb25kZW5zZWQiLCJyZXNwb25zaXZlIiksZnVsbF93aWR0aCA9IEYsZml4ZWRfdGhlYWQgPSBUKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIENyZWF0aW5nIE1vZGVsIEZpbGUgey50YWJzZXQgLnRhYnNldC1waWxsc30gIA0KDQojIyBBZGRpdGlvbmFsIERhdGEgUHJlcCAgDQoNCiMjIyBSZW1vdmUgVmFyaWFibGVzIHdoaWNoIGFyZSBiYXNlZCBvZmYgY3VycmVudCBoaXR0aW5nIG51bWJlcnMgIA0KDQpOb3QgYWxsIHZhcmlhYmxlcyBjYW4gYmUgdXNlZCBmb3IgcHJlZGljdGl2ZSBtb2RlbGluZy4gIFZhcmlhYmxlcyB0aGF0IGdvIGludG8gdGhlIHBlcmNlbnRpbGUgcmFua2luZyBvciBhcmUgbm9uLW5vcm1hbGl6ZWQgbWV0cmljcyBjcmVhdGVkIGFmdGVyIHRoZSBmYWN0IChzdWNoIGFzIGBXQVJgIC0gV2lucyBhYm92ZSBSZXBsYWNlbWVudCBvciBgUlNgIC0gUmF3IFJ1biBTdXBwb3J0KSBzaG91bGQgYmUgcmVtb3ZlZC4gSG93ZXZlciwgbWV0cmljcyB0aGF0IGFyZSBub3JtYWxpemVkIGJ5IGEgcGVyIHBpdGNoIGJhc2lzIChzdWNoIGFzIGB3RkIvQ2ApIGNhbiByZW1haW4gYXMgd2UgZXhwZWN0IHBpdGNoZXJzIHRvIGhhdmUgc2ltaWxhciBwZXJmb3JtYW5jZSBpbiB0aGVzZSBtZXRyaWNzIG9uZSB5ZWFyIG91dC4gIA0KDQpgYGB7cn0NCiNCZSBjYXJlZnVsIGFib3V0IFJTIC0gUnVuIFN1cHBvcnQgYW5kIFJTLzkNCg0KI0NyZWF0aW5nIGEgbmV3IGRhdGFzZXQgdG8ga2VlcCBvcmlnaW5hbCBpbnRhY3QNCmRmX3BpdGNoaW5nX2luaXQzID0gZGZfcGl0Y2hpbmdfaW5pdDIgJT4lIA0KICBzZWxlY3QgKC1OYW1lKQ0KYGBgDQoNCg0KTGFnZ2VkIFBlcmNlbnRpbGUgKGBfc2hhcmVgKSBWYXJpYWJsZXMgY2FuIGJlIHVzZWQgZm9yIHByZWRpY3RpdmUgbW9kZWxpbmcuIEhvd2V2ZXIgc2luY2UgdGhlc2UgdmFyaWFibGVzIHdlcmUgY3JlYXRlZCBmb3IgdGhlIFdvcnRoIG1ldHJpYyB0aGV5IG11c3QgYWxzbyBiZSByZW1vdmVkIGZvciBtb2RlbGluZyBwdXJwb3Nlcy4gIA0KDQpgYGB7cn0NCg0KI09yZGVyIHRoZSBkYXRhc2V0IGJ5IGxhZyBjb2x1bW5zDQpkZl9waXRjaGluZ19pbml0NCA9ICBhcnJhbmdlKGRmX3BpdGNoaW5nX2luaXQzLCBwbGF5ZXJpZCxTZWFzb24pICNwbGF5ZXJpZCBpcyB0aGUgRmFuZ3JhcGggaWQgYXNzaWduZWQgdG8gZWFjaCBwbGF5ZXINCg0KIyBDb252ZXJ0IGRhdGFmcmFtZSB0byBkYXRhLnRhYmxlIGZvcm1hdA0KRFRfcGl0Y2hlcjIgPSBkYXRhLnRhYmxlKGRmX3BpdGNoaW5nX2luaXQ0KQ0KDQojZGVzaWduYXRlIGNvbHVtbnMgdG8gbGFnIC0ganVzdCB0aGUgbmV3IHNoYXJlcw0KY29sczEgPSAoYygnV2luc19zaGFyZScsJ1NPX3NoYXJlJywnU1Zfc2hhcmUnLCAnRVJBX3NoYXJlJywnV0hJUF9zaGFyZScsJ0hMRF9zaGFyZScsJ1dvcnRoJykpDQphbnNjb2xzID0gcGFzdGUoImxhZyIsIGNvbHMxLCBzZXA9Il8iKSANCkRUX3BpdGNoZXIyWywgKGFuc2NvbHMpIDo9IGRhdGEudGFibGU6OnNoaWZ0KC5TRCwgMSwgTkEsICJsYWciKSxieSA9J3BsYXllcmlkJywgLlNEY29scz1jb2xzMV0gI0NyZWF0ZSAxIHBlcmlvZCBsYWdzIGJ5IHllYXINCg0KZGZfcGl0Y2hpbmdfZmluYWwgPSBhcy5kYXRhLmZyYW1lKERUX3BpdGNoZXIyKSAlPiUgDQogIHNlbGVjdCgtYyhXaW5zX3NoYXJlLFNPX3NoYXJlLFNWX3NoYXJlLCBFUkFfc2hhcmUsV0hJUF9zaGFyZSxITERfc2hhcmUpKSU+JQ0Kc2VsZWN0KC1GSVAsLShSQVI6V1BBKSwtKHdGQjp3Q0gpLC0oYEVSQS1gOmB4RklQLWApLA0KICAgICAgIC1TSUVSQSwtKGBSQTktV0FSYDpgQWdlIFJuZ2ApLC1rd0VSQSwtKGB3Q0ggKHBpKWA6YHdTTCAocGkpYCksLShgSy85K2A6YEhSL0ZCJStgKSkgJT4lIHNlbGVjdCgtVywtU08sLVNWLC1ITEQsLVdfSVAsLVNPX0lQLC1TVl9JUCwtV0hJUCwtRVJBLC1ITERfSVApDQoNCmBgYA0KDQojIyMgQ3JlYXRpbmcgVHJhaW5pbmcvVGVzdCBTcGxpdA0KDQpXZSBzcGxpdCB0aGUgZGF0YSBpbnRvIFRyYWluaW5nIERhdGEgKHdoaWNoIGlzIHVzZWQgdG8gY3JlYXRlIHRoZSBtb2RlbCkgYW5kIHRlc3QgZGF0YSAod2hpY2ggaXMgdXNlZCB0byB2YWxpZGF0ZSB0aGUgbW9kZWwpDQoNCmBgYHtyfQ0KDQpzZXQuc2VlZCgxNTY3NCkgICMgRm9yIHJlcHJvZHVjaWJpbGl0eQ0KIyBDcmVhdGUgaW5kZXggZm9yIHRlc3RpbmcgYW5kIHRyYWluaW5nIGRhdGENCmluVHJhaW4gPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5ID0gZGZfcGl0Y2hpbmdfZmluYWwkV29ydGgsIHAgPSAwLjgwLCBsaXN0ID0gRkFMU0UpDQojIHN1YnNldCBwaXRjaGluZyBkYXRhIGZvciB0cmFpbmluZw0KdHJfMjAyMSA8LSBkZl9waXRjaGluZ19maW5hbFtpblRyYWluLF0NCiMgc3Vic2V0IHRoZSByZXN0IHRvIHRlc3QgYW5kIHZhbGlkYXRlIHRyYWluZWQgbW9kZWwNCnRlXzIwMjEgPC0gZGZfcGl0Y2hpbmdfZmluYWxbLWluVHJhaW4sXQ0KDQpucm93KHRyXzIwMjEpL25yb3coZGZfcGl0Y2hpbmdfZmluYWwpICNjaGVjayBpZiBzcGxpdCBpcyAwLjgNCg0KYGBgDQoNCiMjIyBUcmVhdCBNaXNzaW5nIERhdGEgYnkgSW1wdXRpbmcgTWVhbiBWYWx1ZQ0KDQpWdHJlYXQgUGFja2FnZSBpbiBSIGlzIGV4Y2VsbGVudCBmb3IgdHJlYXRpbmcgZGF0YSBiZWZvcmUgdXNpbmcgZm9yIG1vZGVsaW5nLiBBZGRpdGlvbmFsIGRvY3VtZW50YXRpb24gY2FuIGJlIGZvdW5kIFtoZXJlLl0oaHR0cHM6Ly93aW52ZWN0b3IuZ2l0aHViLmlvL3Z0cmVhdC9pbmRleC5odG1sKQ0KDQpgYGB7cn0NCnRyZWF0X3BsYW5fMjAyMSA8LSB2dHJlYXQ6OmRlc2lnblRyZWF0bWVudHNaKA0KICBkZnJhbWUgPSB0cl8yMDIxLCAjIHRyYWluaW5nIGRhdGENCiAgdmFybGlzdCA9IGNvbG5hbWVzKHRyXzIwMjEpICU+JSAuWy4gIT0gImhpdHRpbmdfc2NvcmUxIl0sICMgaW5wdXQgdmFyaWFibGVzID0gYWxsIHRyYWluaW5nIGRhdGEgY29sdW1ucywgZXhjZXB0IHJhbmRvbQ0KICBjb2RlUmVzdHJpY3Rpb24gPSBjKCJjbGVhbiIsICJpc0JBRCIsICJsZXYiKSwgIyBkZXJpdmVkIHZhcmlhYmxlcyB0eXBlcyAoZHJvcCBjYXRfUCkNCiAgdmVyYm9zZSA9IEZBTFNFKSAjIHN1cHByZXNzIG1lc3NhZ2VzDQoNCiNjbGVhbiBzdGFuZHMgZm9yIGNsZWFuZWQgbnVtZXJpY2FsIHZhcmlhYmxlLCBpc0JBRCBpbmRpY2F0ZXMgdGhhdCBhIHZhbHVlIHJlcGxhY2VtZW50IGhhcyBvY2N1cnJlZCAod2hpY2ggaW5kaWNhdGVzIGEgbWlzc2luZyB2YWx1ZSBpbiB0aGlzIGNhc2UpLCBhbmQgbGV2IGlzIGEgYmluYXJ5IGluZGljYXRvciB3aGV0aGVyIGEgcGFydGljdWxhciB2YWx1ZSBvZiB0aGF0IGNhdGVnb3JpY2FsIHZhcmlhYmxlIHdhcyBwcmVzZW50LiAgDQoNCiMjIyMgQ2hlY2tpbmcgU2NvcmVmcmFtZQ0KDQpzY29yZV9mcmFtZSA8LSB0cmVhdF9wbGFuXzIwMjEkc2NvcmVGcmFtZSAlPiUgDQogIHNlbGVjdCh2YXJOYW1lLCBvcmlnTmFtZSwgY29kZSkNCg0KaGVhZChzY29yZV9mcmFtZSkNCg0KDQp0cl90cmVhdGVkXzIwMjEgPC0gdnRyZWF0OjpwcmVwYXJlKHRyZWF0X3BsYW5fMjAyMSwgdHJfMjAyMSkNCnRlX3RyZWF0ZWRfMjAyMSA8LSB2dHJlYXQ6OnByZXBhcmUodHJlYXRfcGxhbl8yMDIxLCB0ZV8yMDIxKQ0KDQoNCnRyZWF0X3BsYW5fMjAyMSA8LSB2dHJlYXQ6OmRlc2lnblRyZWF0bWVudHNaKA0KICBkZnJhbWUgPSBEVF9waXRjaGVyMiwgIyB0cmFpbmluZyBkYXRhDQogIHZhcmxpc3QgPSBjb2xuYW1lcyhEVF9waXRjaGVyMikgJT4lIC5bLiAhPSAiaGl0dGluZ19zY29yZTEiXSwgIyBpbnB1dCB2YXJpYWJsZXMgPSBhbGwgdHJhaW5pbmcgZGF0YSBjb2x1bW5zLCBleGNlcHQgcmFuZG9tDQogIGNvZGVSZXN0cmljdGlvbiA9IGMoImNsZWFuIiwgImlzQkFEIiwgImxldiIpLCAjIGRlcml2ZWQgdmFyaWFibGVzIHR5cGVzIChkcm9wIGNhdF9QKQ0KICB2ZXJib3NlID0gRkFMU0UpICMgc3VwcHJlc3MgbWVzc2FnZXMNCg0KDQp0b3RhbF90cmVhdGVkXzIwMjFfcGl0Y2hpbmcgPC0gdnRyZWF0OjpwcmVwYXJlKHRyZWF0X3BsYW5fMjAyMSwgRFRfcGl0Y2hlcjIpDQoNCiN0cl90cmVhdGVkID0gdHINCiN0ZV90cmVhdGVkID0gdGUNCg0KZGltKHRyX3RyZWF0ZWRfMjAyMSkgI25vdGUgdGhlcmUgYXJlIGR1bW1pZXMgZm9yIGVhY2ggcGxheWVyIGFuZCB0ZWFtDQoNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIENoZWNrIERpc3RyaWJ1dGlvbiBvZiBUcmFpbmluZyBQb3B1bGF0aW9uDQoNClRoZSBwb3B1bGF0aW9uIHVzZWQgZm9yIFRyYWluaW5nIHNob3VsZCBiZSBpbmRpY2F0aXZlIG9mIFRvdGFsIFBvcHVsYXRpb24NCg0KYGBge3J9DQoNCmdncGxvdDI6OnFwbG90KHRyX3RyZWF0ZWRfMjAyMSRXb3J0aCwgbWFpbj0iVHJhaW5pbmcgU2V0IikgKyBnZW9tX2hpc3RvZ3JhbShjb2xvdXI9ImJsYWNrIiwgZmlsbD0ic3RlZWxibHVlIikgKyB0aGVtZV9idygpDQoNCiNUaGUgc2tld25lc3MgaXMgYWN0dWFsbHkgYSBiaXQgYmV0dGVyIHRoYW4gdGhlIG92ZXJhbGwgZGF0YSBzZXQNCnNrZXduZXNzKHRyX3RyZWF0ZWRfMjAyMSRXb3J0aCkgDQoNCg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIFJ1bm5pbmcgWEdib29zdCBNb2RlbCB7LnRhYnNldCAudGFic2V0LXBpbGxzfQ0KDQpUbyBrZWVwIHRoaW5ncyBzaW1wbGUgd2l0aCBtb2RlbGluZywgd2UnbGwgdHVybiB0aGUgdHJhaW5pbmcgZGF0YSBpbnRvIHNpbXBsZSBpbnB1dCB2YXJpYWJsZXMgZm9yIGBjYXJldDo6dHJhaW5gLCBkcm9wcGluZyB0aGUgcmVzcG9uc2UgdmFyaWFibGUgYW5kIGNvbnZlcnRpbmcgdGhlIGRhdGEgZnJhbWUgdG8gYSBtYXRyaXguIERvY3VtZW50YXRpb24gZm9yIHRoaXMgYXBwcm9hY2ggdG8gWEdib29zdCBjYW4gYmUgZm91bmQgW2hlcmUuXShodHRwczovL3d3dy5rYWdnbGUuY29tL3BlbGtvamEvdmlzdWFsLXhnYm9vc3QtdHVuaW5nLXdpdGgtY2FyZXQpDQoNCiMjIFR1bmluZyB0aGUgTW9kZWwNCg0KIyMjIEluaXRpYWwgTm9uLVR1bmVkIE1vZGVsDQoNCkJyZWFrIHRoZSBkYXRhIHNldCBpbnRvIHggYW5kIHkgaW5wdXRzIHdpdGggeCBiZWluZyBhIG1hdHJpeC4gYCJfaXNCQUQiYCBpcyBhIGNhdGVnb3J5IGNyZWF0ZWQgYnkgdGhlIGBWdHJlYXRgIHBhY2thZ2UgaW4gY2FzZSB5b3Ugd2FudCB0byBpZGVudGlmeSByb3dzDQoNCmBgYHtyfQ0KaW5wdXRfeCA8LSBhcy5tYXRyaXgoKCh0cl90cmVhdGVkXzIwMjEpKSU+JQ0KICAgc2VsZWN0KC1Xb3J0aCkgJT4lICAgICAgICAgICAgICAgICAgICAgIA0KICAgc2VsZWN0KCFlbmRzX3dpdGggKCJfaXNCQUQiKSkpDQoNCmlucHV0X3kgPC0gdHJfdHJlYXRlZF8yMDIxJFdvcnRoDQoNCmBgYA0KDQoqKlhHQm9vc3Qgd2l0aCBEZWZhdWx0IEh5cGVycGFyYW1ldGVyczoqKlwNClRoZSBWYXJpYWJsZSBJbXBvcnRhbmNlIChgY2FyZXQ6OnZhckltcCh4Z2JfYmFzZV8yMDIxLCBzY2FsZSA9IEZgKSBmcm9tIHRoZSBjYXJldCBwYWNrYWdlIHNob3dzIHRoZSBjb250cmlidXRpb24gb2YgZWFjaCB2YXJpYWJsZSB0byB0aGUgaW5pdGlhbCBtb2RlbC4gU2luY2UgdGhpcyBpcyB1bnR1bmVkLCB3ZSBjYW4gZXhwZWN0IHRoZSBwZXJjZW50YWdlIGltcG9yYW50YW5jZSB0byBjaGFuZ2UgYXMgdGhlIG1vZGVscyBpdGVyYXRlIHRocm91Z2ggcG90ZW50aWFsIGh5cGVycGFyYW1ldGVycy5cDQoqWEdCb29zdCBkb2N1bWVudGF0aW9uIGNhbiBiZSBmb3VuZCBmb3IgbW9yZSBnZW5lcmFsIG1vZGVscyBbaGVyZS5dKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vY29kZS9ydGF0bWFuL21hY2hpbmUtbGVhcm5pbmctd2l0aC14Z2Jvb3N0LWluLXIvbm90ZWJvb2spKg0KDQpgYGB7cn0NCg0KI0RlZmF1bHRzIGZvciB4Z2Jvb3N0IG1vZGVsDQpncmlkX2RlZmF1bHQgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSAxMDAsDQogIG1heF9kZXB0aCA9IDYsDQogIGV0YSA9IDAuMywNCiAgZ2FtbWEgPSAwLA0KICBjb2xzYW1wbGVfYnl0cmVlID0gMSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IDEsDQogIHN1YnNhbXBsZSA9IDENCikNCg0KI1RoaXMgaXMgYSBibGFuayB0cmFpbl9jb250cm9sIHNldCwgdGhpcyB3aWxsIGJlIHVwZGF0ZWQgYWZ0ZXINCnRyYWluX2NvbnRyb2wgPC0gY2FyZXQ6OnRyYWluQ29udHJvbCgNCiAgbWV0aG9kID0gIm5vbmUiLA0KICB2ZXJib3NlSXRlciA9IEZBTFNFLCAjIG5vIHRyYWluaW5nIGxvZw0KICBhbGxvd1BhcmFsbGVsID0gVFJVRSAjIEZBTFNFIGZvciByZXByb2R1Y2libGUgcmVzdWx0cyANCikNCg0KeGdiX2Jhc2VfMjAyMSA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94LA0KICB5ID0gaW5wdXRfeSwNCiAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgdHVuZUdyaWQgPSBncmlkX2RlZmF1bHQsDQogIG1ldGhvZCA9ICJ4Z2JUcmVlIiwNCiAgdmVyYm9zZSA9IFRSVUUNCikNCg0KY2FyZXQ6OnZhckltcCh4Z2JfYmFzZV8yMDIxLCBzY2FsZSA9IEYgICkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgRnVydGhlciBWYXJpYWJsZSBTZWxlY3Rpb24NCg0KIyMjIFJlbW92ZSByZWR1bmRhbnQgYW5kIGhpZ2hseSBjb3JyZWxhdGVkIHZhcmlhYmxlcw0KDQpTZWxlY3Rpb24gUmVtb3ZhbCBTdGVwIDE6IENoZWNrIGZvciBoaWdoIGNvcnJlbGF0aW9uc1wNCk5vcm1hbGx5LCB0aGlzIHN0ZXAgaXMgZG9uZSBlYXJseSwgYnV0IHRob3NlIHN0ZXBzIHdlcmUgcmVzZXJ2ZWQgZm9yIHByZXBhcmluZyB0aGUgZGF0YQ0KDQpgYGB7cn0NCg0KZGVwX2NvcjEgPC0gdChhcy5kYXRhLmZyYW1lKGNvcih0cl90cmVhdGVkXzIwMjFbICwgY29sbmFtZXModHJfdHJlYXRlZF8yMDIxKSAhPSAiV29ydGgiXSwNCiAgICAgICAgICAgICAgICB0cl90cmVhdGVkXzIwMjEkV29ydGgpKSkNCmRlcF9jb3IxIDwtDQphcy5kYXRhLmZyYW1lKHQoYXMuZGF0YS5mcmFtZShkZXBfY29yMSklPiUgDQogIHNlbGVjdCghc3RhcnRzX3dpdGgoImxhZyIpKSAlPiUgI3JlbW92ZSBsYWcgdmFyaWFibGVzDQogIHNlbGVjdCghY29udGFpbnMoIl9pc0JBRCIpKSkpIA0KDQpkZXBfY29yMSA8LSB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbihkZXBfY29yMSwiVkFSSUFCTEVTIiklPiUgI3JlbW92ZSBpbmRpY2F0b3JzIGZvciBtaXNzaW5nIGRhdGENCiAgZmlsdGVyKFYxID4gMC40MHxWMSA8IC0wLjMpDQoNCmRlcF9jb3IxDQoNCmRlcF9jb3IyIDwtIGNvbG5hbWVzKHJvd190b19uYW1lcyh0KGRlcF9jb3IxKSxyb3dfbnVtYmVyID0gMSkpDQpgYGANCg0KTGV0J3MgUmVtb3ZlIHZhcmlhYmxlcyB3aXRoIGhpZ2ggY29ycmVsYXRpb24gdG8gd29ydGggbWV0cmljLCBhbmQgbWV0cmljcyB0aGF0IGFyZSBjYWxjdWxhdGVkIGFmdGVyIGEgcGxheWVyJ3MgcGVyZm9ybWFuY2UgKHN1Y2ggYXMgYFdQQWAvYFJFMjRgKSBvciByZWR1bmRhbnQgKGBSU19JUGApDQoNCmBgYHtyfQ0KDQppbnB1dF94IDwtIGFzLm1hdHJpeCgoKHRyX3RyZWF0ZWRfMjAyMSkpJT4lDQogICBzZWxlY3QoLVdvcnRoKSAlPiUgI1JlbW92ZSBzb21lIHZhcmlhYmxlcyB2YXJpYWJsZXMNCiAgICAgc2VsZWN0ICgtUlNfSVAsLUVSX0lQLC1SX0lQLC1SRVcsLVJFMjQsLUNsdXRjaCwtV1BBX3NsYXNoX0xJLC1TZWFzb24gI1JlbW92ZSByZWR1bmRhbnQgdmFyaWFibGVzIG9yIG5vbi93ZWlnaHRlZCB2YXJpYWJsZXMNCikgJT4lICAgICAgDQpzZWxlY3QoIWVuZHNfd2l0aCAoIl9pc0JBRCIpKSkgI2luZGljYXRvciB2YXJpYWJsZSBmb3IgbWlzc2luZyBkYXRhDQoNCmlucHV0X3kgPC0gdHJfdHJlYXRlZF8yMDIxJFdvcnRoDQoNCg0KYGBgDQoNClJ1biB0aGUgbW9kZWwgb24gdGhlIG5ldyBkYXRhc2V0IHRvIG1ha2Ugc3VyZSB0aGUgdmFyaWFibGUgaW1wb3J0YW5jZXMgbG9vayBmaW5lDQoNCmBgYHtyfQ0KDQojTm90ZSBUcmFpbmluZyBwYXJhbWV0ZXJzIHdlcmUgc2V0IGluIGluaXRpYWwgbW9kZWwgc2V0IHVwDQp4Z2JfYmFzZV8yMDIxIDwtIGNhcmV0Ojp0cmFpbigNCiAgeCA9IGlucHV0X3gsDQogIHkgPSBpbnB1dF95LA0KICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICB0dW5lR3JpZCA9IGdyaWRfZGVmYXVsdCwNCiAgbWV0aG9kID0gInhnYlRyZWUiLA0KICB2ZXJib3NlID0gVFJVRQ0KKQ0KDQpjYXJldDo6dmFySW1wKHhnYl9iYXNlXzIwMjEsIHNjYWxlID0gRiAgKQ0KDQoNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgTW9kZWwgd2l0aCBuZXcgZGF0YQ0KDQojIyMgVHVuaW5nIEFsbCBIeXBlcnBhcmFtZXRlcnMNCg0KQSB0dW5lIGdyaWQgYWxsb3dzIHVzIHRvIHRlc3QgYSBsYXJnZSBhbW91bnQgb2YgaHlwZXItcGFyYW1ldGVycyBhbmQgZmluZCB0aGUgbW9kZWwgd2l0aCB0aGUgbG93ZXN0IFJNU0UgZm9yIHByZWRpY3Rpb25zLlwNCkhvd2V2ZXIsIFRoZSBtb3JlIHZhbHVlcyB5b3Ugd2FudCB0byB0ZXN0IGFuZCB0aGUgZ3JlYXRlciB0aGUgYW1vdW50IG9mIENyb3NzLUZvbGQgVmFsaWRhdGlvbnMgKGBtZXRob2QgPSAiY3YiYCksIHRoZSBncmVhdGVyIHRoZSBjb21wdXRhdGlvbmFsIHRpbWUgaXQgd2lsbCB0YWtlLiBNb3JlIGluZm9ybWF0aW9uIG9uIHRoZSBzcGVjaWZpYyBwYXJhbWV0ZXJzIGNhbiBiZSBmb3VuZCBbaGVyZS5dKGh0dHBzOi8vd3d3LmhhY2tlcmVhcnRoLmNvbS9wcmFjdGljZS9tYWNoaW5lLWxlYXJuaW5nL21hY2hpbmUtbGVhcm5pbmctYWxnb3JpdGhtcy9iZWdpbm5lcnMtdHV0b3JpYWwtb24teGdib29zdC1wYXJhbWV0ZXItdHVuaW5nLXIvdHV0b3JpYWwvKQ0KDQpgYGB7cn0NCg0KIyBtYXhpbXVtIG51bWJlciBvZiB0cmVlcw0KbnJvdW5kcyA8LSAxMDAwDQoNCiMgbm90ZSB0byBzdGFydCBucm91bmRzIGZyb20gMjAwLCBhcyBzbWFsbGVyIGxlYXJuaW5nIHJhdGVzIHJlc3VsdCBpbiBlcnJvcnMgc28NCiMgYmlnIHdpdGggbG93ZXIgc3RhcnRpbmcgcG9pbnRzIHRoYXQgdGhleSdsbCBtZXNzIHRoZSBzY2FsZXMNCnR1bmVfZ3JpZCA8LSBleHBhbmQuZ3JpZCgNCiAgbnJvdW5kcyA9IHNlcShmcm9tID0gMTAwLCB0byA9IG5yb3VuZHMsIGJ5ID0gNTApLA0KICBldGEgPSBjKDAuMDEsIDAuMDI1LCAwLjA1LCAwLjA3NSwgMC4xKSwNCiAgbWF4X2RlcHRoID0gYygyLCA0LCA2LCA4LCAxMCksDQogIGdhbW1hID0gMCwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IDEsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSAxLA0KICBzdWJzYW1wbGUgPSAxDQopDQoNCnR1bmVfY29udHJvbCA8LSBjYXJldDo6dHJhaW5Db250cm9sKA0KICBtZXRob2QgPSAiY3YiLCAjIGNyb3NzLXZhbGlkYXRpb24NCiAgbnVtYmVyID0gNSwgIyB3aXRoIG4gZm9sZHMgDQogICMjIE5vdGUgdGhpcyB3YXMgIyBvdXQgaW4gdGhlIG9yaWdpbmFsIGNvZGUNCiAgI2luZGV4ID0gY3JlYXRlRm9sZHModHJfdHJlYXRlZCRJZF9jbGVhbiksICMgZml4IHRoZSBmb2xkcw0KICB2ZXJib3NlSXRlciA9IEZBTFNFLCAjIG5vIHRyYWluaW5nIGxvZw0KICBhbGxvd1BhcmFsbGVsID0gRkFMU0UgIyBGQUxTRSBmb3IgcmVwcm9kdWNpYmxlIHJlc3VsdHMgDQopDQoNCg0KDQpgYGANCg0KKlJ1bm5pbmcgdGhlIGluaXRpYWwgdHVuaW5nIG1vZGVsKg0KDQpgYGB7cn0NCiNOb3RlIEkgd2lsbCBiZSB0aW1pbmcgdGhlc2UgcnVucyB0byBnaXZlIGFuIGVzdGltYXRlIG9uIGhvdyBsb25nIHRoaXMgbW9kZWwgdGFrZXMgdG8gcnVuDQpzdGFydF90aW1lIDwtIFN5cy50aW1lKCkNCg0KeGdiX3R1bmVfMjAyMSA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94LA0KICB5ID0gaW5wdXRfeSwNCiAgdHJDb250cm9sID0gdHVuZV9jb250cm9sLA0KICB0dW5lR3JpZCA9IHR1bmVfZ3JpZCwNCiAgbWV0aG9kID0gInhnYlRyZWUiLA0KICB2ZXJib3NlID0gRkFMU0UNCiAgLHZlcmJvc2l0eSA9IDANCikNCg0KZW5kX3RpbWUgPC0gU3lzLnRpbWUoKQ0KDQplbmRfdGltZSAtIHN0YXJ0X3RpbWUNCg0KYGBgDQoNCipUdW5pbmcgUGxvdCBhbmQgVmFyaWFibGUgSW1wb3J0YW5jZSoNCg0KYGBge3J9DQp2YXJJbXAoeGdiX3R1bmVfMjAyMSwgc2NhbGUgPSBGICApIA0KDQoNCiMgaGVscGVyIGZ1bmN0aW9uIGZvciB0aGUgcGxvdHMNCnR1bmVwbG90IDwtIGZ1bmN0aW9uKHgsIHByb2JzID0gLjkwKSB7DQogIGdncGxvdCh4KSArDQogICAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKHF1YW50aWxlKHgkcmVzdWx0cyRSTVNFLCBwcm9icyA9IHByb2JzKSwgbWluKHgkcmVzdWx0cyRSTVNFKSkpICsNCiAgICB0aGVtZV9idygpDQp9DQoNCnR1bmVwbG90KHhnYl90dW5lXzIwMjEpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyBGaW5lIFR1bmluZyBNb2RlbCAgDQoNCiMjIyMgU2Vjb25kIFR1bmluZzogTWF4aW11bSBEZXB0aCBhbmQgTWluaW11bSBDaGlsZCBXZWlnaHQgIA0KDQpBZnRlciBmaXhpbmcgdGhlIGxlYXJuaW5nIHJhdGUgdG8gdGhlIGJlc3QgdHVuZSBmcm9tIHRoZSBwcmV2aW91cyBpdGVyYXRpb24gYW5kIHdlJ2xsIGFsc28gc2V0IG1heGltdW0gZGVwdGggdG8gMyArLTEgKG9yICsyIGlmIG1heF9kZXB0aCA9PSAyKSB0byBleHBlcmltZW50IGEgYml0IGFyb3VuZCB0aGUgc3VnZ2VzdGVkIGJlc3QgdHVuZSBpbiBwcmV2aW91cyBzdGVwLiBUaGVuLCB3ZWxsIGZpeCBtYXhpbXVtIGRlcHRoIGFuZCBtaW5pbXVtIGNoaWxkIHdlaWdodC4gIA0KDQpgYGB7cn0NCnR1bmVfZ3JpZDIgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSBzZXEoZnJvbSA9IDUwLCB0byA9IG5yb3VuZHMsIGJ5ID0gNTApLA0KICBldGEgPSB4Z2JfdHVuZV8yMDIxJGJlc3RUdW5lJGV0YSwNCiAgbWF4X2RlcHRoID0gaWZlbHNlKHhnYl90dW5lXzIwMjEkYmVzdFR1bmUkbWF4X2RlcHRoID09IDIsDQogICAgYyh4Z2JfdHVuZV8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aDo0KSwNCiAgICB4Z2JfdHVuZV8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCAtIDE6eGdiX3R1bmVfMjAyMSRiZXN0VHVuZSRtYXhfZGVwdGggKyAxKSwNCiAgZ2FtbWEgPSAwLA0KICBjb2xzYW1wbGVfYnl0cmVlID0gMSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMiwgMyksDQogIHN1YnNhbXBsZSA9IDENCikNCg0KeGdiX3R1bmUyXzIwMjEgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gaW5wdXRfeCwNCiAgeSA9IGlucHV0X3ksDQogIHRyQ29udHJvbCA9IHR1bmVfY29udHJvbCwNCiAgdHVuZUdyaWQgPSB0dW5lX2dyaWQyLA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBUUlVFDQopDQoNCnR1bmVwbG90KHhnYl90dW5lMl8yMDIxKQ0KDQp4Z2JfdHVuZTJfMjAyMSRiZXN0VHVuZQ0KDQp2YXJJbXAoeGdiX3R1bmUyXzIwMjEsIHNjYWxlID0gRiAgKSANCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIyBUaGlyZCBUdW5pbmc6IENvbHVtbiBhbmQgUm93IFNhbXBsaW5nDQoNCmBgYHtyfQ0KDQp0dW5lX2dyaWQzIDwtIGV4cGFuZC5ncmlkKA0KICBucm91bmRzID0gc2VxKGZyb20gPSA1MCwgdG8gPSBucm91bmRzLCBieSA9IDUwKSwNCiAgZXRhID0geGdiX3R1bmVfMjAyMSRiZXN0VHVuZSRldGEsDQogIG1heF9kZXB0aCA9IHhnYl90dW5lMl8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCwNCiAgZ2FtbWEgPSAwLA0KICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjQsIDAuNiwgMC44LCAxLjApLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0geGdiX3R1bmUyXzIwMjEkYmVzdFR1bmUkbWluX2NoaWxkX3dlaWdodCwNCiAgc3Vic2FtcGxlID0gYygwLjUsIDAuNzUsIDEuMCkNCikNCg0KeGdiX3R1bmUzXzIwMjEgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gaW5wdXRfeCwNCiAgeSA9IGlucHV0X3ksDQogIHRyQ29udHJvbCA9IHR1bmVfY29udHJvbCwNCiAgdHVuZUdyaWQgPSB0dW5lX2dyaWQzLA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBUUlVFDQopDQoNCnR1bmVwbG90KHhnYl90dW5lM18yMDIxLCBwcm9icyA9IC45NSkNCg0KeGdiX3R1bmUzXzIwMjEkYmVzdFR1bmUNCg0KdmFySW1wKHhnYl90dW5lM18yMDIxLCBzY2FsZSA9IEYgICkgDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIyMgRm91cnRoIFR1bmluZzogR2FtbWENCg0KTmV4dCwgd2UgYWdhaW4gcGljayB0aGUgYmVzdCB2YWx1ZXMgZnJvbSBwcmV2aW91cyBzdGVwLCBhbmQgbm93IHdpbGwgc2VlIHdoZXRoZXIgY2hhbmdpbmcgdGhlIGdhbW1hIGhhcyBhbnkgZWZmZWN0IG9uIHRoZSBtb2RlbCBmaXQ6DQoNCmBgYHtyfQ0KdHVuZV9ncmlkNCA8LSBleHBhbmQuZ3JpZCgNCiAgbnJvdW5kcyA9IHNlcShmcm9tID0gNTAsIHRvID0gbnJvdW5kcywgYnkgPSA1MCksDQogIGV0YSA9IHhnYl90dW5lXzIwMjEkYmVzdFR1bmUkZXRhLA0KICBtYXhfZGVwdGggPSB4Z2JfdHVuZTJfMjAyMSRiZXN0VHVuZSRtYXhfZGVwdGgsDQogIGdhbW1hID0gYygwLCAwLjA1LDAuMSwgMC4yLDAuNCwgMC41LCAwLjcsIDAuOSwgMS4wKSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHhnYl90dW5lM18yMDIxJGJlc3RUdW5lJGNvbHNhbXBsZV9ieXRyZWUsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSB4Z2JfdHVuZTJfMjAyMSRiZXN0VHVuZSRtaW5fY2hpbGRfd2VpZ2h0LA0KICBzdWJzYW1wbGUgPSB4Z2JfdHVuZTNfMjAyMSRiZXN0VHVuZSRzdWJzYW1wbGUNCikNCg0KeGdiX3R1bmU0XzIwMjEgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gaW5wdXRfeCwNCiAgeSA9IGlucHV0X3ksDQogIHRyQ29udHJvbCA9IHR1bmVfY29udHJvbCwNCiAgdHVuZUdyaWQgPSB0dW5lX2dyaWQ0LA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBUUlVFDQopDQoNCnR1bmVwbG90KHhnYl90dW5lNF8yMDIxKQ0KDQp4Z2JfdHVuZTRfMjAyMSRiZXN0VHVuZQ0KDQp2YXJJbXAoeGdiX3R1bmU0XzIwMjEsIHNjYWxlID0gRiAgKSANCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMjIyBGaWZ0aCBUdW5pbmc6IFJlZHVjaW5nIHRoZSBMZWFybmluZyBSYXRlDQoNCk5vdywgd2UgaGF2ZSB0dW5lZCB0aGUgaHlwZXJwYXJhbWV0ZXJzIGFuZCBjYW4gc3RhcnQgcmVkdWNpbmcgdGhlIGxlYXJuaW5nIHJhdGUgdG8gZ2V0IHRvIHRoZSBmaW5hbCBtb2RlbDoNCg0KYGBge3J9DQpzdGFydF90aW1lIDwtIFN5cy50aW1lKCkNCg0KdHVuZV9ncmlkNSA8LSBleHBhbmQuZ3JpZCgNCiAgbnJvdW5kcyA9IHNlcShmcm9tID0gMTAwLCB0byA9IDEwMDAwLCBieSA9IDc1KSwNCiAgIGV0YSA9IGMoMC4wMSwgMC4wMTUsIDAuMDI1LDAuMDM1LCAwLjA1LDAuNzUsIDAuMSksDQogIG1heF9kZXB0aCA9IHhnYl90dW5lMl8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCwNCiAgZ2FtbWEgPSB4Z2JfdHVuZTRfMjAyMSRiZXN0VHVuZSRnYW1tYSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHhnYl90dW5lM18yMDIxJGJlc3RUdW5lJGNvbHNhbXBsZV9ieXRyZWUsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSB4Z2JfdHVuZTJfMjAyMSRiZXN0VHVuZSRtaW5fY2hpbGRfd2VpZ2h0LA0KICBzdWJzYW1wbGUgPSB4Z2JfdHVuZTNfMjAyMSRiZXN0VHVuZSRzdWJzYW1wbGUNCikNCg0KDQoNCnhnYl90dW5lNV8yMDIxIDwtIGNhcmV0Ojp0cmFpbigNCiAgeCA9IGlucHV0X3gsDQogIHkgPSBpbnB1dF95LA0KICB0ckNvbnRyb2wgPSB0dW5lX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gdHVuZV9ncmlkNSwNCiAgbWV0aG9kID0gInhnYlRyZWUiLA0KICB2ZXJib3NlID0gVFJVRQ0KKQ0KDQojdHVuZXBsb3QoeGdiX3R1bmU1XzIwMjEpDQoNCmVuZF90aW1lIDwtIFN5cy50aW1lKCkNCg0KZW5kX3RpbWUgLSBzdGFydF90aW1lDQoNCnhnYl90dW5lNV8yMDIxJGJlc3RUdW5lDQoNCnZhckltcCh4Z2JfdHVuZTVfMjAyMSwgc2NhbGUgPSBGICApIA0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMjIEZpdHRpbmcgRmluYWwgTW9kZWwNCg0KYGBge3J9DQoNCihmaW5hbF9ncmlkXzIwMjEgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRucm91bmRzLA0KICBldGEgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRldGEsDQogIG1heF9kZXB0aCA9IHhnYl90dW5lNV8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCwNCiAgZ2FtbWEgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRnYW1tYSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHhnYl90dW5lNV8yMDIxJGJlc3RUdW5lJGNvbHNhbXBsZV9ieXRyZWUsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRtaW5fY2hpbGRfd2VpZ2h0LA0KICBzdWJzYW1wbGUgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRzdWJzYW1wbGUNCikpDQoNCih4Z2JfbW9kZWxfMjAyMSA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94LA0KICB5ID0gaW5wdXRfeSwNCiAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgdHVuZUdyaWQgPSBmaW5hbF9ncmlkXzIwMjEsDQogIG1ldGhvZCA9ICJ4Z2JUcmVlIiwNCiAgdmVyYm9zZSA9IFRSVUUNCikpDQoNCnZhckltcCh4Z2JfbW9kZWxfMjAyMSwgc2NhbGUgPSBGICApIA0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyBNb2RlbCBQZXJmb3JtYW5jZQ0KDQojIyMgQ2hlY2tpbmcgTW9kZWwgb24gVGVzdCBTcGxpdCBEYXRhDQoNCldlIGRvbid0IG5lZWQgdG8gbG9vayB0b28gY2xvc2VseSBhdCBhcmUgdHJhaW5pbmcgZGF0YSBhcyBYZ2Jvb3N0IHdpbGwgaGVhdmlseSBvdmVyZml0IHRoZSBtb2RlbCBiYXNlZCBvbiB0aGF0IGRhdGEuIFRoZSBtb3JlIGltcG9ydGFudCBwYXJ0IGlzIGhvdyB0aGUgbW9kZWwgcGVyZm9ybXMgb24gaW4gcHJlZGljdGluZyBvdXIgVGVzdCBTYW1wbGUgdGhhdCB3YXMgbm90IGluY2x1ZGVkLg0KDQpgYGB7cn0NCg0KDQp5X3ByZWRfdGVzdCA8LSBwcmVkaWN0KHhnYl9tb2RlbF8yMDIxLCBkYXRhLm1hdHJpeCh0ZV90cmVhdGVkXzIwMjEpKQ0KDQp0ZXN0X3N0YXRzPSBjYmluZCgodGVfdHJlYXRlZF8yMDIxJFdvcnRoKSx5X3ByZWRfdGVzdCkNCg0KdGVzdF9zdGF0c1IyID0gY29yKHRlc3Rfc3RhdHNbLDFdLHRlc3Rfc3RhdHNbLDJdKV4yDQoNCnByaW50KHRlc3Rfc3RhdHNSMikNCg0KDQp5X3ByZWRfdHJhaW4gPC0gcHJlZGljdCh4Z2JfbW9kZWxfMjAyMSwgZGF0YS5tYXRyaXgodHJfdHJlYXRlZF8yMDIxKSkNCg0KdHJhaW5fc3RhdHMgPSBjYmluZCgodHJfdHJlYXRlZF8yMDIxJFdvcnRoKSx5X3ByZWRfdHJhaW4pDQoNCnRyYWluX3N0YXRzUjIgPSBjb3IodHJhaW5fc3RhdHNbLDFdLHRyYWluX3N0YXRzWywyXSleMg0KDQpwcmludCh0cmFpbl9zdGF0c1IyKQ0KDQojdGVzdCBkYXRhc2V0DQp4IDwtIHNlbGVjdCh0ZV90cmVhdGVkXzIwMjEsIC1Xb3J0aCkNCnkgPC0gKHRlX3RyZWF0ZWRfMjAyMSRXb3J0aCkNCg0KKHhnYl9tb2RlbF9ybXNlIDwtIE1vZGVsTWV0cmljczo6cm1zZSh5LCBwcmVkaWN0KHhnYl9tb2RlbF8yMDIxLCBuZXdkYXRhID0geCkpKQ0KDQpob2xkb3V0X3ggPC0gc2VsZWN0KHRyX3RyZWF0ZWRfMjAyMSwgLVdvcnRoKQ0KaG9sZG91dF95IDwtIHRyX3RyZWF0ZWRfMjAyMSRXb3J0aA0KDQooeGdiX21vZGVsX3Jtc2UgPC0gTW9kZWxNZXRyaWNzOjpybXNlKGhvbGRvdXRfeSwgcHJlZGljdCh4Z2JfbW9kZWxfMjAyMSwgbmV3ZGF0YSA9IGhvbGRvdXRfeCkpKQ0KDQoNCmBgYA0KDQojIyMjIEdyYXBoaWNhbCBSZXByZXNlbnRhdGlvbiBvZiBNb2RlbA0KDQpgYGB7cn0NCg0KZ2dwbG90Mjo6Z2dwbG90KCkgKw0KICBhZXMoeCA9IHRlc3Rfc3RhdHNbLDFdLCB5ID0gdGVzdF9zdGF0c1ssMl0pICsNCiAgZ2VvbV9qaXR0ZXIoKSArDQogIHhsYWIoIlByZWRpY3RlZCBWYWx1ZXMiKSArDQogIHlsYWIoIkFjdHVhbCBWYWx1ZXMiKSArDQogIGdndGl0bGUoIlJlc3VsdHMgb2YgUGl0Y2hpbmcgTW9kZWwgb24gVGVzdCBEYXRhIikrDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsc2l6ZSA9IDIyLGNvbG9yID0ic3RlZWwgYmx1ZSIpKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikNCg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIENyZWF0aW5nIDIwMjIgUHJvamVjdGlvbnMgZnJvbSBNb2RlbCB7LnRhYnNldCAudGFic2V0LXBpbGxzfQ0KDQojIyBSZS1maXQgbW9kZWwgZm9yIEltcG9ydGFudCBWYXJpYWJsZXMNCg0KTm93IHRoYXQgd2UgaGF2ZSBhbiBhY2NlcHRhYmxlIG1vZGVsLCB3ZSBjYW4gdXNlIGl0IHRvIGNyZWF0ZSBwcm9qZWN0aW9ucyBmb3IgaG93IHdlbGwgd2UgdGhpbmsgcGxheWVycyBzaG91bGQgZG8gaW4gMjAyMiBiYXNlZCBvbiB0aGVpciBoaXR0aW5nIHN0YXRpc3RpY3MgaW4gMjAyMS4gRmlyc3QgbGV0J3MgcmVkdWNlDQoNClN0ZXAgMTogT25seSBrZWVwIHZhcmlhYmxlcyB3aXRoIGhpZ2ggZW5vdWdoIGltcG9ydGFuY2UgaW4gbW9kZWwNCg0KYGBge3J9DQoNCg0KdmlwKHhnYl9tb2RlbF8yMDIxLCBudW1fZmVhdHVyZXMgPSAzMCkgICMgMTAgaXMgdGhlIGRlZmF1bHQsIDMwIGdpdmVzIGEgdmlzdWFsIG9uIHRoZSB0b3AgMzAgbW9zdCBpbXBvcnRhbnQgZmVhdHVyZXMgb2YgdGhlIG1vZGVsDQoNCnVuc2NhbGV2aSA9IHZpKHhnYl9tb2RlbF8yMDIxLCBtZXRob2Q9Im1vZGVsIikgI3Nob3dzIHRoZSBudW1iZXJzIGJlaGluZCB0aGUgcGxvdA0KDQp1bnNjYWxldmkkSW1wb3J0YW5jZV9wZXJjID0gd2l0aCh1bnNjYWxldmksSW1wb3J0YW5jZS9zdW0oSW1wb3J0YW5jZSkpICNhZGRzIHBlcmNlbnRhZ2VzIA0KDQp1bnNjYWxldmkgIyBpbXBvcnRhbmNlIGJ5IHZhcmlhYmxlcw0KDQp2YXJpYWJsZXNfdG9fa2VlcF8yMDIxID0gc3Vic2V0KHVuc2NhbGV2aSwgSW1wb3J0YW5jZV9wZXJjID4gMC4wMDEwKSAlPiUgc2VsZWN0KFZhcmlhYmxlKSAjS2VlcCBWYXJpYWJsZXMgdGhhdCBleHBsYWluIGF0IGxlYXN0IGEgc21hbGwgYW1vdW50IFswLjElXSBvZiB0aGUgbW9kZWwuIFRoaXMgaXMgYSBsb3cgdGhyZXNob2xkIGZvciBpbmNsdXNpb24gLGJ1dCB5b3UgY2FuIGFkanVzdCB0aGlzDQoNCnZhcmlhYmxlc190b19rZWVwXzIwMjFiID0gdCh2YXJpYWJsZXNfdG9fa2VlcF8yMDIxKQ0KDQp2YXJpYWJsZXNfdG9fa2VlcF8yMDIyID0gY29sbmFtZXMocm93X3RvX25hbWVzKHZhcmlhYmxlc190b19rZWVwXzIwMjFiLHJvd19udW1iZXIgPSAxKSkNCg0KdHJfdHJlYXRlZF8yMDIyID0gdHJfdHJlYXRlZF8yMDIxICU+JSAgc2VsZWN0KFdvcnRoLG9uZV9vZih2YXJpYWJsZXNfdG9fa2VlcF8yMDIyKSxzdGFydHNfd2l0aCgiVGVhbV9sZXZfeF8iKSkgI2tlZXAgbW9kZWxlZCBpbXBvcnRhbnQgdmFyaWFibGVzIGFsb25nIHdpdGggdGVhbSBpbmRpY2F0b3IgdmFyaWFibGVzDQoNCnRlX3RyZWF0ZWRfMjAyMiA9IHRlX3RyZWF0ZWRfMjAyMSAlPiUgIHNlbGVjdChXb3J0aCxvbmVfb2YodmFyaWFibGVzX3RvX2tlZXBfMjAyMiksc3RhcnRzX3dpdGgoIlRlYW1fbGV2X3hfIikpDQoNCmlucHV0X3hfMjAyMiA9IGFzLm1hdHJpeChzZWxlY3QodHJfdHJlYXRlZF8yMDIyLCAtV29ydGgpKQ0KDQppbnB1dF95XzIwMjIgPSB0cl90cmVhdGVkXzIwMjIkV29ydGgNCg0KDQoNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KU3RlcCAyOiBSZS1maXQgbW9kZWwgd2l0aCByZWR1Y2VkIHZhcmlhYmxlIHNjb3BlXA0KTm90ZSBmcm9tIHRoZSBiZXN0IHR1bmUgYmVsb3cgdGhlIGBucm91bmRzYCAtIGlzIHRoZSBtYXggSSBzZXQgYWJvdmUgYW5kIGBldGFgIGlzIGF0IHRoZSBsb3dlc3QgcG9zc2libGUgdmFsdWUuIFRoaXMgY291bGQgY2F1c2UgcG90ZW50aWFsIG92ZXJmaXR0aW5nIGlzc3VlcywgYnV0IGZyb20gb3VyIEFjdHVhbCB2cy4gUHJlZGljdGVkIEdyYXBoIHdlIGtub3cgdGhpcyBub3QgdG8gYmUgdGhlIGNhc2UuDQoNCmBgYHtyfQ0KDQoNCihmaW5hbF9ncmlkXzIwMjEgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRucm91bmRzLA0KICBldGEgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRldGEsDQogIG1heF9kZXB0aCA9IHhnYl90dW5lNV8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCwNCiAgZ2FtbWEgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRnYW1tYSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHhnYl90dW5lNV8yMDIxJGJlc3RUdW5lJGNvbHNhbXBsZV9ieXRyZWUsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRtaW5fY2hpbGRfd2VpZ2h0LA0KICBzdWJzYW1wbGUgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRzdWJzYW1wbGUNCikpDQoNCih4Z2JfbW9kZWxfMjAyMiA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94XzIwMjIsDQogIHkgPSBpbnB1dF95XzIwMjIsDQogIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gZmluYWxfZ3JpZF8yMDIxLA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBUUlVFDQopKQ0KDQoNCnZpcCh4Z2JfbW9kZWxfMjAyMiwgbnVtX2ZlYXR1cmVzID0gMzApDQoNCnVuc2NhbGV2aTI0ID0gdmkoeGdiX21vZGVsXzIwMjIsIG1ldGhvZD0ibW9kZWwiKQ0KDQp1bnNjYWxldmkyNCRJbXBvcnRhbmNlX3BlcmMgPSB3aXRoKHVuc2NhbGV2aTI0LEltcG9ydGFuY2Uvc3VtKEltcG9ydGFuY2UpKSANCg0KdW5zY2FsZXZpMjQNCg0KIyBTYXZlIHdvcmsgZm9yIGxhdGVyIHByZWRpY3Rpb24NCg0Kc2F2ZSh4Z2JfbW9kZWxfMjAyMixmaWxlID0gJzIwMjJfUGl0Y2hpbmc2eDZfTW9kZWwuUmRhdGEnKQ0KDQpwaXRjaGluZzZ4NiA9IHhnYl9tb2RlbF8yMDIyDQoNCnBpdGNoaW5naW5wdXQ2eDYgPSBpbnB1dF94XzIwMjINCg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyBHZXQgMjAyMiBsaXN0IG9mIHBsYXllcnMNCg0KIyMjIEFycmFuZ2UgdGhlIERhdGEgc28gdGhlIENvbHVtbnMgYXJlIGluIHRoZSBleGFjdCBvcmRlciBhcyB0aGUgbW9kZWwNCg0KRmlyc3QgbGV0J3MgcHJlcGFyZSBhIGZpbGUgZm9yIHByZWRpY3RpbmcgYmFzZWQgb24gb3VyIG1vZGVsIG9iamVjdA0KDQpgYGB7cn0NCg0KDQp2YXJpYWJsZXNsYWc2eD0gcm93X3RvX25hbWVzKGFzLmRhdGEuZnJhbWUodCh2YXJpYWJsZXNfdG9fa2VlcF8yMDIyKSkscm93X251bWJlciA9IDEpICAlPiUgc2VsZWN0IChzdGFydHNfd2l0aCgibGFnIikpDQoNCnZhcmlhYmxlc19ub2xhZzZ4ID0gKG93bXI6OnJlbW92ZV9wcmVmaXgodmFyaWFibGVzbGFnNngsImxhZyIgLCBzZXAgPSAiXyIpKQ0KDQpEYXRhX1ByZWRpY3RfMjAyMmE2eCA9IHRvdGFsX3RyZWF0ZWRfMjAyMV9waXRjaGluZyAlPiUgc2VsZWN0IChvbmVfb2YoY29sbmFtZXModmFyaWFibGVzX25vbGFnNngpKSxTZWFzb24scGxheWVyaWQpDQoNCmNvbG5hbWVzKERhdGFfUHJlZGljdF8yMDIyYTZ4KSA8LSBwYXN0ZTAoImxhZ18iLCBjb2xuYW1lcyhEYXRhX1ByZWRpY3RfMjAyMmE2eCkpDQoNCkRhdGFfUHJlZGljdF8yMDIyYjZ4ID0gdG90YWxfdHJlYXRlZF8yMDIxX3BpdGNoaW5nICU+JSBzZWxlY3QgKG9uZV9vZihjb2xuYW1lcyh2YXJpYWJsZXNfbm9sYWc2eCkpKQ0KY29sbmFtZXMoRGF0YV9QcmVkaWN0XzIwMjJiNngpID0gY29sbmFtZXModmFyaWFibGVzbGFnNngpDQoNCnZhcmlhYmxlc190b19rZWVwXzIwMjJfbm9sYWc2eCA9IHRvdGFsX3RyZWF0ZWRfMjAyMV9waXRjaGluZyAlPiUgc2VsZWN0KG9uZV9vZih2YXJpYWJsZXNfdG9fa2VlcF8yMDIyKSxTZWFzb24scGxheWVyaWQsc3RhcnRzX3dpdGgoIlRlYW1fbGV2X3hfIikpJT4lIHNlbGVjdCgtb25lX29mKGNvbG5hbWVzKERhdGFfUHJlZGljdF8yMDIyYjZ4KSkpDQoNCg0KRGF0YV9wcmVkaWN0XzIwMjI2eCA9IHNxbGRmKA0KICAiDQogIHNlbGVjdCBhLiosYi4qIGZyb20NCiAgRGF0YV9QcmVkaWN0XzIwMjJhNnggYSwNCiAgdmFyaWFibGVzX3RvX2tlZXBfMjAyMl9ub2xhZzZ4IGINCiAgb24gYi5wbGF5ZXJpZCA9IGEubGFnX3BsYXllcmlkDQogIGFuZCBiLlNlYXNvbiA9IGEubGFnX1NlYXNvbg0KICAiDQopICU+JSBzZWxlY3QoLWxhZ19wbGF5ZXJpZCxsYWdfU2Vhc29uKSAlPiUNCiAgZmlsdGVyKFNlYXNvbiA9PSAyMDIxKSAlPiUgDQogIHNlbGVjdChvbmVfb2YodmFyaWFibGVzX3RvX2tlZXBfMjAyMiksc3RhcnRzX3dpdGgoIlRlYW1fbGV2X3hfIikpDQoNCg0KDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIENyZWF0ZSBQcmVkaWN0aW9ucyBmb3IgTW9kZWwNCg0KIyMjIFJ1biBQcm9qZWN0aW9ucyBvbiBQbGF5ZXJzIHdobyBQbGF5ZWQgaW4gMjAyMQ0KDQpUaGlzIGlzIHRoZSByYXcgcHJlZGljdGlvbiBzY29yZSBwZXIgSVAgZm9yIGVhY2ggcGl0Y2hlcg0KDQpgYGB7cn0NCg0KcGl0Y2hpbmdfcHJlZGljdGlvbnM2eCA9IGFzLmRhdGEuZnJhbWUocHJlZGljdCh4Z2JfbW9kZWxfMjAyMixEYXRhX3ByZWRpY3RfMjAyMjZ4KSkNCg0KbmFtZXMocGl0Y2hpbmdfcHJlZGljdGlvbnM2eCkgPSBjKCJQcmVkaWN0X1Njb3JlIikNCg0KRGF0YV9wcmVkaWN0XzIwMjJfd19QaXRjaGluZ19QcmVkaWN0aW9uczZ4ID0gY2JpbmQoRGF0YV9wcmVkaWN0XzIwMjIscGl0Y2hpbmdfcHJlZGljdGlvbnM2eCkgJT4lIHNlbGVjdChwbGF5ZXJpZCxQcmVkaWN0X1Njb3JlKQ0KDQpoZWFkKERhdGFfcHJlZGljdF8yMDIyX3dfUGl0Y2hpbmdfUHJlZGljdGlvbnM2eCkNCg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgTG9hZCBpbiBMYXRlc3QgMjAyMiBQcm9qZWN0aW9ucyBmb3IgSW5uaW5ncyBQaXRjaGVkDQoNCkRvd25sb2FkZWQgZnJvbSBGYW5HcmFwaHMgW2hlcmUuXShodHRwczovL3d3dy5mYW5ncmFwaHMuY29tL3Byb2plY3Rpb25zLmFzcHg/cG9zPWFsbCZzdGF0cz1waXQmdHlwZT1hdGMmdGVhbT0wJmxnPWFsbCZwbGF5ZXJzPTApDQoNCmBgYHtyfQ0KTGF0ZXN0XzIwMjJfcGl0Y2hpbmdkYXRhX0ZQID0gcmVhZF9jc3YoIkZhbkdyYXBoX0ZhbnRhc3lfQmFzZWJhbGxfUGl0Y2hpbmcuY3N2IikNCg0KTGF0ZXN0XzIwMjJfcGl0Y2hpbmdkYXRhX0ZQDQoNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KQXMgeW91IGNhbiBzZWUgZnJvbSB0aGUgY2hhcnQgYmVsb3cgdGhlcmUgYXJlbid0IG1hbnkgZWxpdGUgcGl0Y2hlcnMgaW4gdGhlIDg3KyBQcmVkaWN0IHNjb3JlIHJhbmdlLiAgDQoNCmBgYHtyLCB3YXJuaW5nID0gRmFsc2V9DQoNCg0KUGl0Y2hpbmdfRGF0YV9Ob25BZGpfUHJvamVjdGlvbnM2eCA9IHNxbGRmKA0KICAiDQogIHNlbGVjdCBhLiosYi5QcmVkaWN0X1Njb3JlDQogIGZyb20gTGF0ZXN0XzIwMjJfcGl0Y2hpbmdkYXRhX0ZQIGEgDQogIGxlZnQgam9pbiANCiAgRGF0YV9wcmVkaWN0XzIwMjJfd19QaXRjaGluZ19QcmVkaWN0aW9uczZ4IGINCiAgb24gYS5wbGF5ZXJpZCA9IGIucGxheWVyaWQNCiAgIg0KKSAlPiUgZmlsdGVyKEFEUDwzNzAgfCBpcy5uYShQcmVkaWN0X1Njb3JlKT09RikNCg0KDQpQaXRjaGluZ19EYXRhX0Fkal9Qcm9qZWN0aW9uczZ4ID0NClBpdGNoaW5nX0RhdGFfTm9uQWRqX1Byb2plY3Rpb25zNnggJT4lIA0KICBtdXRhdGUoDQogICAgQXZnX0lQID0gNjAsDQogICAgQWRqUHJlZGljdF9TY29yZV9yYXcgPSBpZmVsc2UoaXMubmEoUHJlZGljdF9TY29yZSksTkEsUHJlZGljdF9TY29yZSooSVAvQXZnX0lQKSksDQogICAgbWF4X3ByZWRzY29yZT0gbWF4KEFkalByZWRpY3RfU2NvcmVfcmF3LG5hLnJtID0gVCksDQogICAgQWRqUHJlZGljdF9TY29yZSA9IGlmZWxzZSAoaXMubmEoQWRqUHJlZGljdF9TY29yZV9yYXcpLE5BLEFkalByZWRpY3RfU2NvcmVfcmF3ICoxMDAvbWF4X3ByZWRzY29yZSksDQogICAgV0FSX3JhbmsgPSBvcmRlcihvcmRlcihyYW5rKFdBUix0aWVzLm1ldGhvZCA9ICdhdmVyYWdlJyksZGVjcmVhc2luZyA9IFRSVUUpKSwNCiAgICBBZGpQcmVkaWN0X1Njb3JlX1JhbmsgPSBvcmRlcihvcmRlcihyYW5rKEFkalByZWRpY3RfU2NvcmUsdGllcy5tZXRob2QgPSAnYXZlcmFnZScpLGRlY3JlYXNpbmcgPSBUUlVFKSktc3VtKGlzLm5hKEFkalByZWRpY3RfU2NvcmUpKSwNCiAgICAgICAgUmFua3NfQWJvdmVfQURQID0gQURQIC0gQWRqUHJlZGljdF9TY29yZV9SYW5rDQogICkgJT4lIHNlbGVjdCAoTmFtZSxBRFAsV0FSLCBXQVJfcmFuayxBZGpQcmVkaWN0X1Njb3JlICxBZGpQcmVkaWN0X1Njb3JlX1JhbmssUmFua3NfQWJvdmVfQURQKQ0KDQoNCiAgDQoNCmdncGxvdDI6OnFwbG90KFBpdGNoaW5nX0RhdGFfQWRqX1Byb2plY3Rpb25zNngkQWRqUHJlZGljdF9TY29yZSwgbWFpbj0iUHJlZGljdGlvbnMiKSArIGdlb21faGlzdG9ncmFtKGNvbG91cj0iYmxhY2siLCBmaWxsPSJncmV5IikgKyB0aGVtZV9idygpDQoNCg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIDIwMjIgUHJvamVjdGlvbnMgRnVsbA0KDQojIyBUYWJsZSBvZiBQaXRjaGluZyBQcm9qZWN0aW9ucyAoUGxheWVycyB3aG8gRGlkbid0IFBsYXkgaW4gMjAyMSAtIFJlY2lldmUgYW4gTkEpDQoNCkFkalByZWRpY3RfU2NvcmUgYXJlIG5vcm1hbGl6ZWQgdG8gMTAwDQoNCmBgYHtyfQ0KDQp0YWJsZWV4cG9ydCA9DQpQaXRjaGluZ19EYXRhX0Fkal9Qcm9qZWN0aW9uczZ4ICU+JQ0KICBhcnJhbmdlIChBRFAsV0FSKSAlPiUgDQogIGtibCgpICU+JSANCiBrYWJsZV9tYXRlcmlhbChjKCJzdHJpcGVkIiwgImhvdmVyIiwiY29uZGVuc2VkIiwicmVzcG9uc2l2ZSIpLGZ1bGxfd2lkdGggPSBGLGZpeGVkX3RoZWFkID0gVCkNCg0Kc2F2ZV9rYWJsZSh0YWJsZWV4cG9ydCxmaWxlID0gIlBpdGNoaW5nNng2Lmh0bWwiKQ0KDQojdGFibGVleHBvcnQNCg0KDQoNCmBgYA0KDQpUaGlzIGlzIGEgYmV0dGVyIGZvcm1hdHRlZCBUYWJsZQ0KDQpgYGB7cn0NCg0KDQoNCmZ0X2R0IDwtIFBpdGNoaW5nX0RhdGFfQWRqX1Byb2plY3Rpb25zNnhbMTpucm93KFBpdGNoaW5nX0RhdGFfQWRqX1Byb2plY3Rpb25zNngpLCAxOm5jb2woUGl0Y2hpbmdfRGF0YV9BZGpfUHJvamVjdGlvbnM2eCldICU+JSANCiAgZmlsdGVyKEFkalByZWRpY3RfU2NvcmVfUmFuaz4wKSU+JSAgYXJyYW5nZSgoQWRqUHJlZGljdF9TY29yZV9SYW5rKSkNCg0KZnRfZHQkQURQIDwtIGNvbG9yX3RpbGUoIndoaXRlIiwgInJlZCIpKGZ0X2R0JEFEUCkNCg0KZnRfZHQkV0FSIDwtIGNvbG9yX2JhcigibGlnaHRibHVlIikoZnRfZHQkV0FSKQ0KDQpmdF9kdCRBZGpQcmVkaWN0X1Njb3JlPC0gY29sb3JfYmFyKCJsaWdodGJsdWUiKShmdF9kdCRBZGpQcmVkaWN0X1Njb3JlKQ0KDQpmdF9kdCRXQVJfUmFuayA8LSBjb2xvcl90aWxlKCJncmVlbiIsIm9yYW5nZSIpKGZ0X2R0JFdBUl9yYW5rKQ0KDQpmdF9kdCRQcmVkaWN0X1JhbmsgPC0gY29sb3JfdGlsZSgiZ3JlZW4iLCJvcmFuZ2UiKShmdF9kdCRBZGpQcmVkaWN0X1Njb3JlX1JhbmspIA0KDQoNCmZ0X2R0JFJhbmtzX0Fib3ZlX0FEUCA8LSANCiAgaWZlbHNlKA0KICBmdF9kdCRSYW5rc19BYm92ZV9BRFAgPCAwLA0KICBjZWxsX3NwZWMocm91bmQoZnRfZHQkUmFua3NfQWJvdmVfQURQLDIpLCBjb2xvciA9ICJyZWQiLCBpdGFsaWMgPSBUKSwNCiAgY2VsbF9zcGVjKHJvdW5kKGZ0X2R0JFJhbmtzX0Fib3ZlX0FEUCwyKSwgY29sb3IgPSAiZ3JlZW4iLCBpdGFsaWMgPSBUKQ0KKQ0KDQoNCmZ0X2R0MiA8LSBmdF9kdFtjKCJOYW1lIiwgIkFEUCIsICJXQVIiLCAiQWRqUHJlZGljdF9TY29yZSIsICJXQVJfUmFuayIsIlByZWRpY3RfUmFuayIsIlJhbmtzX0Fib3ZlX0FEUCIpXQ0KDQoNCg0KdGFibGVfZXhwb3J0ID0gDQprYmwoZnRfZHQyLCBlc2NhcGUgPSBGKSAlPiUgDQoga2FibGVfbWF0ZXJpYWwoYygic3RyaXBlZCIsICJob3ZlciIsImNvbmRlbnNlZCIsInJlc3BvbnNpdmUiKSxmdWxsX3dpZHRoID0gRixmaXhlZF90aGVhZCA9IFQpICU+JSAgIGNvbHVtbl9zcGVjKDYsIHdpZHRoID0gIjNjbSIpICU+JQ0KICBhZGRfaGVhZGVyX2Fib3ZlKGMoIiAiLCAiU2NvcmVzIiA9IDMsICJSYW5rcyIgPSAyLCIgIikpDQoNCnNhdmVfa2FibGUodGFibGVfZXhwb3J0LGZpbGUgPSAiUGl0Y2hpbmc2eDZfdXBkYXRlZC5odG1sIikNCiAgDQp0YWJsZV9leHBvcnQgIA0KDQoNCg0KDQoNCg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQoNCg0KPC9odG1sPg0K